@flamingo-stack/openframe-frontend-core 0.0.510 → 0.0.511-snapshot.20260804140004
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.map +1 -1
- package/dist/chat-protocol/frames.d.ts +26 -1
- package/dist/chat-protocol/frames.d.ts.map +1 -1
- package/dist/chat-protocol/index.cjs +24 -0
- package/dist/chat-protocol/index.cjs.map +1 -1
- package/dist/chat-protocol/index.js +24 -0
- package/dist/chat-protocol/index.js.map +1 -1
- package/dist/{chunk-GE3JUTW2.js → chunk-27OKD7ZV.js} +44 -14
- package/dist/chunk-27OKD7ZV.js.map +1 -0
- package/dist/{chunk-RHQ7BJKK.cjs → chunk-2WWDZR4D.cjs} +73 -73
- package/dist/{chunk-RHQ7BJKK.cjs.map → chunk-2WWDZR4D.cjs.map} +1 -1
- package/dist/{chunk-J2JUAS4C.cjs → chunk-2XL7UK5Y.cjs} +11 -11
- package/dist/{chunk-J2JUAS4C.cjs.map → chunk-2XL7UK5Y.cjs.map} +1 -1
- package/dist/{chunk-Q4K3TGNP.cjs → chunk-3NECEXDF.cjs} +74 -44
- package/dist/chunk-3NECEXDF.cjs.map +1 -0
- package/dist/{chunk-JOCFPAS6.js → chunk-4NFDSMB5.js} +6 -6
- package/dist/{chunk-SYZFFRVD.cjs → chunk-5BAGE7AI.cjs} +13 -13
- package/dist/{chunk-SYZFFRVD.cjs.map → chunk-5BAGE7AI.cjs.map} +1 -1
- package/dist/{chunk-53JKCFFE.cjs → chunk-5JBDT2MQ.cjs} +12 -12
- package/dist/{chunk-53JKCFFE.cjs.map → chunk-5JBDT2MQ.cjs.map} +1 -1
- package/dist/{chunk-XJVRAKEX.cjs → chunk-67YDSTH7.cjs} +56 -33
- package/dist/chunk-67YDSTH7.cjs.map +1 -0
- package/dist/{chunk-WNOGR5AA.js → chunk-6LMTBNLN.js} +3 -3
- package/dist/{chunk-6FIDTXYC.js → chunk-7AQSXQEL.js} +3 -3
- package/dist/{chunk-JMBIGH4H.cjs → chunk-7VBEOGCR.cjs} +83 -83
- package/dist/{chunk-JMBIGH4H.cjs.map → chunk-7VBEOGCR.cjs.map} +1 -1
- package/dist/{chunk-5AZZQQYS.cjs → chunk-AWLFJMOJ.cjs} +40 -40
- package/dist/{chunk-5AZZQQYS.cjs.map → chunk-AWLFJMOJ.cjs.map} +1 -1
- package/dist/{chunk-RJ3D3PE2.cjs → chunk-DQDPDUI3.cjs} +9 -9
- package/dist/{chunk-RJ3D3PE2.cjs.map → chunk-DQDPDUI3.cjs.map} +1 -1
- package/dist/{chunk-2RLI5XVI.cjs → chunk-F2YI5N7E.cjs} +16 -16
- package/dist/{chunk-2RLI5XVI.cjs.map → chunk-F2YI5N7E.cjs.map} +1 -1
- package/dist/{chunk-LBOWSKGM.js → chunk-FI35LJ3L.js} +5 -5
- package/dist/{chunk-BTUHMCYW.js → chunk-FS3YSNSU.js} +193 -6
- package/dist/chunk-FS3YSNSU.js.map +1 -0
- package/dist/{chunk-RB2EW6IZ.cjs → chunk-GOFTC2HV.cjs} +3690 -3472
- package/dist/chunk-GOFTC2HV.cjs.map +1 -0
- package/dist/{chunk-CTMLY2GN.js → chunk-HGZPH7S5.js} +5 -5
- package/dist/{chunk-7VUKIWF6.cjs → chunk-HIV2GIFP.cjs} +3 -3
- package/dist/{chunk-7VUKIWF6.cjs.map → chunk-HIV2GIFP.cjs.map} +1 -1
- package/dist/{chunk-DBR5JLX3.js → chunk-IHNUMRMU.js} +46 -23
- package/dist/chunk-IHNUMRMU.js.map +1 -0
- package/dist/{chunk-R4HKB3YD.cjs → chunk-IIPBTWQR.cjs} +22 -22
- package/dist/{chunk-R4HKB3YD.cjs.map → chunk-IIPBTWQR.cjs.map} +1 -1
- package/dist/{chunk-TBGSQTJK.js → chunk-IK2WRYVA.js} +6 -6
- package/dist/{chunk-WSAPKKDU.cjs → chunk-JIW5JI3E.cjs} +9 -9
- package/dist/{chunk-WSAPKKDU.cjs.map → chunk-JIW5JI3E.cjs.map} +1 -1
- package/dist/{chunk-MLGGF24T.js → chunk-JKAX5K7C.js} +8 -8
- package/dist/{chunk-UWPT23SA.js → chunk-JO3NPEK6.js} +3 -3
- package/dist/{chunk-HCZJZMU7.js → chunk-JOFGJYIT.js} +3 -3
- package/dist/{chunk-A62C4C4V.cjs → chunk-KI4EGKNV.cjs} +3 -2
- package/dist/chunk-KI4EGKNV.cjs.map +1 -0
- package/dist/{chunk-OB7YDOTK.cjs → chunk-NJADHL2G.cjs} +8 -8
- package/dist/{chunk-OB7YDOTK.cjs.map → chunk-NJADHL2G.cjs.map} +1 -1
- package/dist/{chunk-EYTXD62B.cjs → chunk-OGVIGAMZ.cjs} +232 -45
- package/dist/chunk-OGVIGAMZ.cjs.map +1 -0
- package/dist/{chunk-HX4CJYXL.js → chunk-OT4RV6WY.js} +10 -10
- package/dist/{chunk-Z35QWWJH.cjs → chunk-RJXI25TJ.cjs} +34 -34
- package/dist/{chunk-Z35QWWJH.cjs.map → chunk-RJXI25TJ.cjs.map} +1 -1
- package/dist/{chunk-6TL5XIAW.cjs → chunk-SG25J6LV.cjs} +119 -119
- package/dist/{chunk-6TL5XIAW.cjs.map → chunk-SG25J6LV.cjs.map} +1 -1
- package/dist/{chunk-M4MKQKFF.js → chunk-UBTOOE55.js} +2 -2
- package/dist/{chunk-FEGEC6FY.js → chunk-V5WLIFKN.js} +5660 -5442
- package/dist/chunk-V5WLIFKN.js.map +1 -0
- package/dist/{chunk-EJ3EBYNV.cjs → chunk-VVXUUBML.cjs} +32 -32
- package/dist/{chunk-EJ3EBYNV.cjs.map → chunk-VVXUUBML.cjs.map} +1 -1
- package/dist/{chunk-SRYV4H3M.js → chunk-VZX3SSFL.js} +3 -3
- package/dist/{chunk-5CDPRCPS.cjs → chunk-WVZHCDB5.cjs} +23 -23
- package/dist/{chunk-5CDPRCPS.cjs.map → chunk-WVZHCDB5.cjs.map} +1 -1
- package/dist/{chunk-VDJF4ZAU.js → chunk-XG7FLAKQ.js} +3 -3
- package/dist/{chunk-SZMA65OW.js → chunk-XURRAFIB.js} +2 -2
- package/dist/{chunk-RG2WRBPT.js → chunk-Z3ADCYC5.js} +2 -2
- package/dist/{chunk-2AASLY6D.js → chunk-ZIQMVAGA.js} +3 -3
- package/dist/{chunk-4GPWLGKT.js → chunk-ZRILYK5U.js} +2 -1
- package/dist/components/calendar.cjs +4 -4
- package/dist/components/calendar.js +3 -3
- package/dist/components/case-studies/index.cjs +17 -17
- package/dist/components/case-studies/index.js +7 -7
- package/dist/components/chat/approval-batch-message.d.ts +2 -2
- package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
- 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/entity-cards/deleted-data-card.d.ts +27 -0
- package/dist/components/chat/entity-cards/deleted-data-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat-card-item.d.ts +3 -0
- package/dist/components/chat/hooks/use-chat-card-item.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/components/chat/index.cjs +13 -7
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +12 -6
- package/dist/components/chat/remark-mention-chips.d.ts +5 -2
- package/dist/components/chat/remark-mention-chips.d.ts.map +1 -1
- package/dist/components/chat/remark-strip-citations.d.ts +22 -0
- package/dist/components/chat/remark-strip-citations.d.ts.map +1 -0
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
- package/dist/components/chat/stream/message-mutations.d.ts +49 -5
- package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +7 -7
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/chat/types/message.types.d.ts +11 -4
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +4 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/chat/utils/discuss-ref-prompt.d.ts +48 -0
- package/dist/components/chat/utils/discuss-ref-prompt.d.ts.map +1 -0
- package/dist/components/chat/utils/index.d.ts +2 -0
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.d.ts +5 -3
- package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
- package/dist/components/chat/utils/resolve-fetched-card-href.d.ts +63 -0
- package/dist/components/chat/utils/resolve-fetched-card-href.d.ts.map +1 -0
- package/dist/components/chat/utils/source-row-cta.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +8 -8
- package/dist/components/contact/index.js +7 -7
- package/dist/components/docs/index.cjs +10 -10
- package/dist/components/docs/index.js +9 -9
- package/dist/components/embeds/index.cjs +8 -8
- package/dist/components/embeds/index.js +7 -7
- package/dist/components/faq/index.cjs +9 -9
- package/dist/components/faq/index.js +8 -8
- package/dist/components/features/index.cjs +7 -7
- package/dist/components/features/index.js +6 -6
- package/dist/components/help-center-pages/index.cjs +28 -28
- package/dist/components/help-center-pages/index.js +19 -19
- package/dist/components/icons/index.cjs +3 -3
- package/dist/components/icons/index.js +2 -2
- package/dist/components/index.cjs +253 -247
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +23 -17
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +92 -92
- package/dist/components/meeting-scheduler/index.js +9 -9
- package/dist/components/navigation/index.cjs +7 -7
- package/dist/components/navigation/index.js +6 -6
- package/dist/components/onboarding-guides/index.cjs +11 -11
- package/dist/components/onboarding-guides/index.js +10 -10
- package/dist/components/related-content/index.cjs +9 -9
- package/dist/components/related-content/index.js +8 -8
- package/dist/components/shared/doc-search/resolve-search-result-action.d.ts +11 -3
- package/dist/components/shared/doc-search/resolve-search-result-action.d.ts.map +1 -1
- package/dist/components/shared/doc-search/use-doc-search.d.ts +7 -0
- package/dist/components/shared/doc-search/use-doc-search.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +11 -11
- package/dist/components/tickets/index.js +10 -10
- package/dist/components/ui/actions-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +70 -70
- package/dist/components/ui/file-manager/index.js +3 -3
- package/dist/components/ui/index.cjs +13 -7
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +12 -6
- package/dist/components/ui/markdown/base-components.d.ts.map +1 -1
- package/dist/components/ui/overlay-open-registry.d.ts +50 -0
- package/dist/components/ui/overlay-open-registry.d.ts.map +1 -0
- package/dist/components/ui/portal-container.d.ts +24 -0
- package/dist/components/ui/portal-container.d.ts.map +1 -1
- package/dist/hooks/index.cjs +4 -4
- package/dist/hooks/index.js +3 -3
- package/dist/index.cjs +20 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +24 -14
- package/dist/utils/anchor-id.d.ts +32 -0
- package/dist/utils/anchor-id.d.ts.map +1 -0
- package/dist/utils/content-href.d.ts +27 -6
- package/dist/utils/content-href.d.ts.map +1 -1
- package/dist/utils/index.cjs +213 -152
- 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 +212 -153
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/list-url.d.ts.map +1 -1
- package/dist/utils/same-page-hash-nav.d.ts +17 -6
- package/dist/utils/same-page-hash-nav.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/chat-protocol/decode.ts +43 -0
- package/src/chat-protocol/frames.ts +27 -0
- package/src/components/chat/__tests__/remark-mention-chips.test.ts +67 -0
- package/src/components/chat/approval-batch-message.tsx +2 -2
- package/src/components/chat/chat-message-enhanced.tsx +5 -3
- package/src/components/chat/chat-message-list.tsx +49 -5
- package/src/components/chat/embeddable-chat.tsx +19 -2
- package/src/components/chat/entity-cards/deleted-data-card.tsx +61 -0
- package/src/components/chat/entity-cards/dispatch.tsx +205 -102
- package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +1 -1
- package/src/components/chat/hooks/use-chat-card-item.ts +15 -1
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +28 -4
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +57 -32
- package/src/components/chat/remark-mention-chips.ts +6 -3
- package/src/components/chat/remark-strip-citations.ts +39 -0
- package/src/components/chat/stream/__tests__/chat-stream-reducer-approval-batch.test.ts +196 -0
- package/src/components/chat/stream/chat-stream-reducer.ts +67 -2
- package/src/components/chat/stream/message-mutations.ts +170 -40
- package/src/components/chat/types/component.types.ts +9 -7
- package/src/components/chat/types/message.types.ts +14 -4
- package/src/components/chat/types/unified-chat-state.types.ts +4 -1
- package/src/components/chat/utils/__tests__/discuss-ref-prompt.test.ts +67 -0
- package/src/components/chat/utils/__tests__/resolve-fetched-card-href.test.ts +156 -0
- package/src/components/chat/utils/discuss-ref-prompt.ts +63 -0
- package/src/components/chat/utils/index.ts +16 -0
- package/src/components/chat/utils/message-segment-accumulator.ts +24 -29
- package/src/components/chat/utils/resolve-fetched-card-href.ts +104 -0
- package/src/components/chat/utils/source-row-cta.ts +5 -1
- package/src/components/shared/doc-search/__tests__/resolve-search-result-action.test.ts +145 -0
- package/src/components/shared/doc-search/resolve-search-result-action.ts +46 -10
- package/src/components/shared/doc-search/use-doc-search.ts +50 -14
- package/src/components/ui/__tests__/overlay-open-registry.test.tsx +87 -0
- package/src/components/ui/actions-menu.tsx +65 -2
- package/src/components/ui/dropdown-menu.tsx +62 -12
- package/src/components/ui/markdown/base-components.tsx +26 -0
- package/src/components/ui/overlay-open-registry.tsx +99 -0
- package/src/components/ui/portal-container.tsx +31 -0
- package/src/utils/__tests__/anchor-id.test.ts +139 -0
- package/src/utils/__tests__/content-href.test.ts +74 -0
- package/src/utils/__tests__/list-url.test.ts +11 -1
- package/src/utils/anchor-id.ts +106 -0
- package/src/utils/content-href.ts +113 -13
- package/src/utils/index.ts +8 -0
- package/src/utils/list-url.ts +6 -1
- package/src/utils/same-page-hash-nav.ts +28 -9
- package/dist/chunk-A62C4C4V.cjs.map +0 -1
- package/dist/chunk-BTUHMCYW.js.map +0 -1
- package/dist/chunk-DBR5JLX3.js.map +0 -1
- package/dist/chunk-EYTXD62B.cjs.map +0 -1
- package/dist/chunk-FEGEC6FY.js.map +0 -1
- package/dist/chunk-GE3JUTW2.js.map +0 -1
- package/dist/chunk-Q4K3TGNP.cjs.map +0 -1
- package/dist/chunk-RB2EW6IZ.cjs.map +0 -1
- package/dist/chunk-XJVRAKEX.cjs.map +0 -1
- /package/dist/{chunk-JOCFPAS6.js.map → chunk-4NFDSMB5.js.map} +0 -0
- /package/dist/{chunk-WNOGR5AA.js.map → chunk-6LMTBNLN.js.map} +0 -0
- /package/dist/{chunk-6FIDTXYC.js.map → chunk-7AQSXQEL.js.map} +0 -0
- /package/dist/{chunk-LBOWSKGM.js.map → chunk-FI35LJ3L.js.map} +0 -0
- /package/dist/{chunk-CTMLY2GN.js.map → chunk-HGZPH7S5.js.map} +0 -0
- /package/dist/{chunk-TBGSQTJK.js.map → chunk-IK2WRYVA.js.map} +0 -0
- /package/dist/{chunk-MLGGF24T.js.map → chunk-JKAX5K7C.js.map} +0 -0
- /package/dist/{chunk-UWPT23SA.js.map → chunk-JO3NPEK6.js.map} +0 -0
- /package/dist/{chunk-HCZJZMU7.js.map → chunk-JOFGJYIT.js.map} +0 -0
- /package/dist/{chunk-HX4CJYXL.js.map → chunk-OT4RV6WY.js.map} +0 -0
- /package/dist/{chunk-M4MKQKFF.js.map → chunk-UBTOOE55.js.map} +0 -0
- /package/dist/{chunk-SRYV4H3M.js.map → chunk-VZX3SSFL.js.map} +0 -0
- /package/dist/{chunk-VDJF4ZAU.js.map → chunk-XG7FLAKQ.js.map} +0 -0
- /package/dist/{chunk-SZMA65OW.js.map → chunk-XURRAFIB.js.map} +0 -0
- /package/dist/{chunk-RG2WRBPT.js.map → chunk-Z3ADCYC5.js.map} +0 -0
- /package/dist/{chunk-2AASLY6D.js.map → chunk-ZIQMVAGA.js.map} +0 -0
- /package/dist/{chunk-4GPWLGKT.js.map → chunk-ZRILYK5U.js.map} +0 -0
|
@@ -36,6 +36,7 @@ import type { ChatRef } from '../chat-ref.types'
|
|
|
36
36
|
import { useChatCardItem } from '../hooks/use-chat-card-item'
|
|
37
37
|
import { handleChatNavClick } from '../utils/nav-click-handler'
|
|
38
38
|
import { resolveSourceRowCTA, resolveSourceIcon, sourceRowCtxFromRuntime } from '../utils/source-row-cta'
|
|
39
|
+
import { resolveFetchedCardHref, readFetchedCardTitle } from '../utils/resolve-fetched-card-href'
|
|
39
40
|
import { resolveHrefForRuntime } from '../utils/chat-nav-resolution'
|
|
40
41
|
import {
|
|
41
42
|
computeIsNewTab,
|
|
@@ -48,6 +49,7 @@ import { SourceActionButton } from '../source-action-button'
|
|
|
48
49
|
import { NavLinkAnchorViaRuntime } from '../nav-link-anchor-via-runtime'
|
|
49
50
|
import { defaultBuildProductReleaseCardProps } from './product-release-card-defaults'
|
|
50
51
|
import { ChatVideoEntityCard } from './chat-video-entity-card'
|
|
52
|
+
import { DeletedDataCard } from './deleted-data-card'
|
|
51
53
|
import { BlockCard } from './block-card'
|
|
52
54
|
import { BlogCardSkeleton } from './blog-card'
|
|
53
55
|
import { CaseStudyCardSkeleton } from './case-study-card'
|
|
@@ -1000,8 +1002,15 @@ type ChatCardRegistryEntry =
|
|
|
1000
1002
|
* resolved ref — so the wrapper sees a non-null `href`, the
|
|
1001
1003
|
* pre-computed `isNewTab` reflects the actual destination, and
|
|
1002
1004
|
* the click goes through `handleChatNavClick` (no silent
|
|
1003
|
-
* bypass of embed-mode / close-on-nav).
|
|
1005
|
+
* bypass of embed-mode / close-on-nav). Takes precedence over the
|
|
1006
|
+
* `composeContentUrl` fallback below. */
|
|
1004
1007
|
fallbackHref?: (item: any) => string | null
|
|
1008
|
+
/** Opt OUT of the post-fetch `composeContentUrl` fallback (see
|
|
1009
|
+
* `resolveFetchedCardHref`). Set for types with NO public destination
|
|
1010
|
+
* — the seam would otherwise synthesize `<contentOrigin>/<type>/<id>`
|
|
1011
|
+
* from its default branch and hand the user a 404. Types with a real
|
|
1012
|
+
* route (hosted, or covered by a host `override`) leave it unset. */
|
|
1013
|
+
noComposedHref?: boolean
|
|
1005
1014
|
}
|
|
1006
1015
|
|
|
1007
1016
|
interface FinancialCardConfig {
|
|
@@ -1017,26 +1026,34 @@ const FINANCIAL_CARD_CONFIGS: Record<string, FinancialCardConfig> = {
|
|
|
1017
1026
|
balance_sheet: { label: 'Balance sheet', icon: () => <BankIcon size={24} /> },
|
|
1018
1027
|
cash_flow: { label: 'Cash flow', icon: () => <CoinsExchangeCurrencyIcon size={24} /> },
|
|
1019
1028
|
}
|
|
1020
|
-
|
|
1029
|
+
/** Expand a per-family config map into registry entries — ONE loop shell
|
|
1030
|
+
* for every card family (financial, github, program, roadmap); adding a
|
|
1031
|
+
* family is a config map + one `registryEntries(...)` call. */
|
|
1032
|
+
function registryEntries<C>(
|
|
1033
|
+
configs: Record<string, C>,
|
|
1034
|
+
build: (cfg: C) => ChatCardRegistryEntry,
|
|
1035
|
+
): Record<string, ChatCardRegistryEntry> {
|
|
1021
1036
|
const out: Record<string, ChatCardRegistryEntry> = {}
|
|
1022
|
-
for (const [docType, cfg] of Object.entries(
|
|
1023
|
-
out[docType] = {
|
|
1024
|
-
mode: 'no-fetch',
|
|
1025
|
-
label: cfg.label,
|
|
1026
|
-
bareInline: true,
|
|
1027
|
-
render: (chatRef, opts) => (
|
|
1028
|
-
<GenericFinancialChatCard
|
|
1029
|
-
chatRef={chatRef}
|
|
1030
|
-
icon={cfg.icon()}
|
|
1031
|
-
isNewTab={opts.isNewTab}
|
|
1032
|
-
discuss={opts.discuss}
|
|
1033
|
-
/>
|
|
1034
|
-
),
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
+
for (const [docType, cfg] of Object.entries(configs)) out[docType] = build(cfg)
|
|
1037
1038
|
return out
|
|
1038
1039
|
}
|
|
1039
1040
|
|
|
1041
|
+
function financialRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1042
|
+
return registryEntries(FINANCIAL_CARD_CONFIGS, (cfg) => ({
|
|
1043
|
+
mode: 'no-fetch',
|
|
1044
|
+
label: cfg.label,
|
|
1045
|
+
bareInline: true,
|
|
1046
|
+
render: (chatRef, opts) => (
|
|
1047
|
+
<GenericFinancialChatCard
|
|
1048
|
+
chatRef={chatRef}
|
|
1049
|
+
icon={cfg.icon()}
|
|
1050
|
+
isNewTab={opts.isNewTab}
|
|
1051
|
+
discuss={opts.discuss}
|
|
1052
|
+
/>
|
|
1053
|
+
),
|
|
1054
|
+
}))
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1040
1057
|
interface GitHubCardConfig {
|
|
1041
1058
|
label: string
|
|
1042
1059
|
kind: GitHubActivityKind
|
|
@@ -1050,19 +1067,15 @@ const GITHUB_CARD_CONFIGS: Record<string, GitHubCardConfig> = {
|
|
|
1050
1067
|
github_pr_review_public: { label: 'GitHub review (public)', kind: 'pr_review' },
|
|
1051
1068
|
}
|
|
1052
1069
|
function githubRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
),
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
return out
|
|
1070
|
+
return registryEntries(GITHUB_CARD_CONFIGS, (cfg) => ({
|
|
1071
|
+
mode: 'no-fetch',
|
|
1072
|
+
label: cfg.label,
|
|
1073
|
+
bareInline: true,
|
|
1074
|
+
render: (chatRef, opts) => (
|
|
1075
|
+
<GitHubChatCard chatRef={chatRef} kind={cfg.kind} isNewTab={opts.isNewTab}
|
|
1076
|
+
discuss={opts.discuss} />
|
|
1077
|
+
),
|
|
1078
|
+
}))
|
|
1066
1079
|
}
|
|
1067
1080
|
|
|
1068
1081
|
type ProgramConfigKey = 'podcast' | 'webinar' | 'event'
|
|
@@ -1077,28 +1090,24 @@ const PROGRAM_CARD_CONFIGS: Record<string, ProgramCardConfig> = {
|
|
|
1077
1090
|
event: { label: 'Event', configKey: 'event', contentRefType: 'event' },
|
|
1078
1091
|
}
|
|
1079
1092
|
function programRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
),
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
return out
|
|
1093
|
+
return registryEntries(PROGRAM_CARD_CONFIGS, (cfg) => ({
|
|
1094
|
+
mode: 'fetch',
|
|
1095
|
+
label: cfg.label,
|
|
1096
|
+
contentRefType: cfg.contentRefType,
|
|
1097
|
+
bareInline: true,
|
|
1098
|
+
skeleton: () => <ProgramCardSkeleton size="sm" />,
|
|
1099
|
+
render: (item, chatRef, opts) => (
|
|
1100
|
+
<ProgramChatCard
|
|
1101
|
+
item={item}
|
|
1102
|
+
chatRef={chatRef}
|
|
1103
|
+
isNewTab={opts.isNewTab}
|
|
1104
|
+
discuss={opts.discuss}
|
|
1105
|
+
configKey={cfg.configKey}
|
|
1106
|
+
label={cfg.configKey.charAt(0).toUpperCase() + cfg.configKey.slice(1)}
|
|
1107
|
+
ogPlaceholder={opts?.extras?.buildOgPlaceholderUrl?.(item?.title ?? '') ?? null}
|
|
1108
|
+
/>
|
|
1109
|
+
),
|
|
1110
|
+
}))
|
|
1102
1111
|
}
|
|
1103
1112
|
|
|
1104
1113
|
type RoadmapCardType = 'roadmap_item' | 'delivery_item' | 'internal_task'
|
|
@@ -1106,6 +1115,8 @@ interface RoadmapEntryConfig {
|
|
|
1106
1115
|
label: string
|
|
1107
1116
|
cardType: RoadmapCardType
|
|
1108
1117
|
contentRefType: string
|
|
1118
|
+
/** See `ChatCardRegistryEntry.noComposedHref`. */
|
|
1119
|
+
noComposedHref?: boolean
|
|
1109
1120
|
}
|
|
1110
1121
|
const ROADMAP_CARD_CONFIGS: Record<string, RoadmapEntryConfig> = {
|
|
1111
1122
|
roadmap_item: { label: 'Roadmap item', cardType: 'roadmap_item', contentRefType: 'roadmap_item' },
|
|
@@ -1118,34 +1129,50 @@ const ROADMAP_CARD_CONFIGS: Record<string, RoadmapEntryConfig> = {
|
|
|
1118
1129
|
label: 'Internal task',
|
|
1119
1130
|
cardType: 'internal_task',
|
|
1120
1131
|
contentRefType: 'internal_task',
|
|
1132
|
+
// Internal ClickUp work has no public destination on any platform — the
|
|
1133
|
+
// seam's default branch would synthesize `<hub>/internal_task/<id>`.
|
|
1134
|
+
noComposedHref: true,
|
|
1121
1135
|
},
|
|
1122
1136
|
}
|
|
1123
1137
|
function roadmapRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
return out
|
|
1138
|
+
return registryEntries(ROADMAP_CARD_CONFIGS, (cfg) => ({
|
|
1139
|
+
mode: 'fetch',
|
|
1140
|
+
label: cfg.label,
|
|
1141
|
+
contentRefType: cfg.contentRefType,
|
|
1142
|
+
bareInline: true,
|
|
1143
|
+
...(cfg.noComposedHref ? { noComposedHref: true } : {}),
|
|
1144
|
+
skeleton: () => <RoadmapCardSkeleton size="sm" />,
|
|
1145
|
+
render: (item, chatRef, opts) => (
|
|
1146
|
+
<RoadmapChatCard
|
|
1147
|
+
item={item}
|
|
1148
|
+
chatRef={chatRef}
|
|
1149
|
+
isNewTab={opts.isNewTab}
|
|
1150
|
+
discuss={opts.discuss}
|
|
1151
|
+
cardType={cfg.cardType}
|
|
1152
|
+
/>
|
|
1153
|
+
),
|
|
1154
|
+
}))
|
|
1144
1155
|
}
|
|
1145
1156
|
|
|
1146
1157
|
const CHAT_CARD_REGISTRY: Record<string, ChatCardRegistryEntry> = {
|
|
1147
1158
|
// ───────── no-fetch: ChatRef carries everything ─────────
|
|
1148
1159
|
...githubRegistryEntries(),
|
|
1160
|
+
// Generic TOMBSTONE for entities a chat action deleted (ClickUp task
|
|
1161
|
+
// today; any deletable entity tomorrow). No fetch, no navigation —
|
|
1162
|
+
// the entity no longer resolves anywhere; the placeholder keeps the
|
|
1163
|
+
// thread's integrity (Teams/Slack "This message was deleted" pattern).
|
|
1164
|
+
deleted_data: {
|
|
1165
|
+
mode: 'no-fetch',
|
|
1166
|
+
label: 'Deleted',
|
|
1167
|
+
bareInline: true,
|
|
1168
|
+
render: (chatRef) => (
|
|
1169
|
+
<DeletedDataCard
|
|
1170
|
+
title={chatRef.title ?? null}
|
|
1171
|
+
entityLabel={(chatRef.metadata?.entity_label as string | undefined) ?? null}
|
|
1172
|
+
recoveryNote={(chatRef.metadata?.recovery_note as string | undefined) ?? null}
|
|
1173
|
+
/>
|
|
1174
|
+
),
|
|
1175
|
+
},
|
|
1149
1176
|
slack_message: {
|
|
1150
1177
|
mode: 'no-fetch',
|
|
1151
1178
|
label: 'Slack message',
|
|
@@ -1420,6 +1447,17 @@ function ChatCardNavWrap({
|
|
|
1420
1447
|
const onClickCapture = (e: React.MouseEvent<HTMLElement>) => {
|
|
1421
1448
|
if (!href) return
|
|
1422
1449
|
const targetEl = e.target as HTMLElement
|
|
1450
|
+
// Only claim clicks that physically happened INSIDE this card.
|
|
1451
|
+
//
|
|
1452
|
+
// The card's "⋯" menu renders through a React PORTAL: its DOM lives in the
|
|
1453
|
+
// panel's portal host, but React still bubbles its events through this
|
|
1454
|
+
// subtree — so this capture handler sees them first and, being a capture
|
|
1455
|
+
// phase, runs BEFORE the menu row's own `stopPropagation`. That swallowed
|
|
1456
|
+
// the menu's trailing "↗ Open in new tab" button: `executeNavigation`
|
|
1457
|
+
// called `preventDefault()` and routed the card's href in the SAME tab,
|
|
1458
|
+
// so the anchor's `target="_blank"` never applied.
|
|
1459
|
+
const host = e.currentTarget as HTMLElement | null
|
|
1460
|
+
if (!host?.contains(targetEl)) return
|
|
1423
1461
|
// Buttons rendered INSIDE the card's outer `<a>` (e.g. RoadmapCard
|
|
1424
1462
|
// vote buttons, ImageGallery thumbnails) bubble up with
|
|
1425
1463
|
// `closest('a')` truthy — without this guard, clicking them would
|
|
@@ -1516,24 +1554,60 @@ export function ChatCardLoader({
|
|
|
1516
1554
|
// `contentRefType` is empty so the hook returns `isLoading=false` and
|
|
1517
1555
|
// `item=undefined`, which we ignore.
|
|
1518
1556
|
const fetchEntry = entry && entry.mode === 'fetch' ? entry : null
|
|
1519
|
-
const { item, isLoading } = useChatCardItem<any>(
|
|
1557
|
+
const { item, isLoading, isError, isFetched } = useChatCardItem<any>(
|
|
1520
1558
|
fetchEntry?.contentRefType ?? '',
|
|
1521
1559
|
fetchEntry ? resolvedChatRef.id : '',
|
|
1522
1560
|
)
|
|
1523
1561
|
if (!entry) return null
|
|
1524
1562
|
|
|
1525
|
-
// Apply
|
|
1563
|
+
// Apply the post-fetch URL fallback (the ref carried no `externalUrl`).
|
|
1526
1564
|
// We mutate `resolvedChatRef.url` BEFORE computing isNewTab so the
|
|
1527
1565
|
// wrapper's interceptor sees the destination the user will actually
|
|
1528
1566
|
// visit. `safeHref` blocks `javascript:` / `data:` payloads even
|
|
1529
1567
|
// though the registry callers compose hub-internal strings today.
|
|
1530
|
-
|
|
1568
|
+
//
|
|
1569
|
+
// Two sources, in order:
|
|
1570
|
+
// 1. the registry's per-type `fallbackHref` — an explicit non-content
|
|
1571
|
+
// destination (marketing campaign → `/admin/...`);
|
|
1572
|
+
// 2. the host's `composeContentUrl` seam via `resolveFetchedCardHref` —
|
|
1573
|
+
// the SAME resolver page cards and SSE chat cards go through. This is
|
|
1574
|
+
// what makes Mingo/NATS cards clickable: that transport ships bare
|
|
1575
|
+
// `[card://type:id]` markers with no refs metadata, so the ref reaches
|
|
1576
|
+
// us with `url: null` and `resolveSourceRowCTA` has nothing to route.
|
|
1577
|
+
const composedHref =
|
|
1578
|
+
fetchEntry && !resolvedChatRef.url && item && !fetchEntry.fallbackHref && !fetchEntry.noComposedHref
|
|
1579
|
+
? resolveFetchedCardHref({
|
|
1580
|
+
contentRefType: fetchEntry.contentRefType,
|
|
1581
|
+
id: resolvedChatRef.id,
|
|
1582
|
+
item,
|
|
1583
|
+
composeContentUrl: runtime.composeContentUrl,
|
|
1584
|
+
})
|
|
1585
|
+
: null
|
|
1586
|
+
const hrefResolvedChatRef: ChatRef =
|
|
1531
1587
|
fetchEntry && !resolvedChatRef.url && item && fetchEntry.fallbackHref
|
|
1532
1588
|
? {
|
|
1533
1589
|
...resolvedChatRef,
|
|
1534
1590
|
url: safeHref(fetchEntry.fallbackHref(item)),
|
|
1535
1591
|
}
|
|
1536
|
-
:
|
|
1592
|
+
: composedHref
|
|
1593
|
+
? {
|
|
1594
|
+
...resolvedChatRef,
|
|
1595
|
+
url: safeHref(composedHref.href),
|
|
1596
|
+
targetPlatform: composedHref.targetPlatform ?? resolvedChatRef.targetPlatform ?? null,
|
|
1597
|
+
}
|
|
1598
|
+
: resolvedChatRef
|
|
1599
|
+
|
|
1600
|
+
// Title enrichment, same synthetic-ref gap as the href above: a Mingo
|
|
1601
|
+
// `[card://type:id]` marker produces `title: <id>` because the transport
|
|
1602
|
+
// ships no ref metadata. Downstream consumers that read `ref.title` — most
|
|
1603
|
+
// visibly the "Ask Mingo" prompt, which would otherwise send
|
|
1604
|
+
// "Tell me more about 86ad3qvv5" — get the row's real title once it loads.
|
|
1605
|
+
// A ref that already carries a distinct title (the SSE path) is untouched.
|
|
1606
|
+
const fetchedTitle = fetchEntry && item ? readFetchedCardTitle(item) : null
|
|
1607
|
+
const finalChatRef: ChatRef =
|
|
1608
|
+
fetchedTitle && (!hrefResolvedChatRef.title || hrefResolvedChatRef.title === hrefResolvedChatRef.id)
|
|
1609
|
+
? { ...hrefResolvedChatRef, title: fetchedTitle }
|
|
1610
|
+
: hrefResolvedChatRef
|
|
1537
1611
|
|
|
1538
1612
|
// Pre-compute new-tab decision ONCE here (the same rule chips use).
|
|
1539
1613
|
// Render branches that pass `target` / `rel` to their card pull this
|
|
@@ -1590,6 +1664,23 @@ export function ChatCardLoader({
|
|
|
1590
1664
|
{children}
|
|
1591
1665
|
</ChatCardNavWrap>
|
|
1592
1666
|
)
|
|
1667
|
+
// ONE render tail for both modes — bare-inline cards skip the discuss
|
|
1668
|
+
// wrapper; everything else gets the ChatCardWithDiscuss chrome. Kept
|
|
1669
|
+
// here (not per-branch) so the two paths can never drift on the
|
|
1670
|
+
// discuss-menu wiring.
|
|
1671
|
+
const finish = (node: React.ReactNode) =>
|
|
1672
|
+
entry.bareInline ? (
|
|
1673
|
+
<>{navWrap(node)}</>
|
|
1674
|
+
) : (
|
|
1675
|
+
<ChatCardWithDiscuss
|
|
1676
|
+
chatRef={finalChatRef}
|
|
1677
|
+
onDiscuss={onDiscuss}
|
|
1678
|
+
onDisplay={onDisplay}
|
|
1679
|
+
displayAction={entry.displayAction}
|
|
1680
|
+
>
|
|
1681
|
+
{navWrap(node)}
|
|
1682
|
+
</ChatCardWithDiscuss>
|
|
1683
|
+
)
|
|
1593
1684
|
if (entry.mode === 'no-fetch') {
|
|
1594
1685
|
// Synthetic-ref gate. `chat-message-enhanced.tsx` builds a minimal
|
|
1595
1686
|
// `{ type, id, title: cardId, url: null }` ChatRef when the LLM
|
|
@@ -1612,35 +1703,47 @@ export function ChatCardLoader({
|
|
|
1612
1703
|
// Fetch-mode types already handle this gracefully: a synthetic
|
|
1613
1704
|
// id leads to a fetch miss → `!item` → null at line ~1034.
|
|
1614
1705
|
if (!finalChatRef.sourceRepo) return null
|
|
1615
|
-
|
|
1616
|
-
return navWrap(entry.render(finalChatRef, renderOpts))
|
|
1617
|
-
}
|
|
1618
|
-
return (
|
|
1619
|
-
<ChatCardWithDiscuss
|
|
1620
|
-
chatRef={finalChatRef}
|
|
1621
|
-
onDiscuss={onDiscuss}
|
|
1622
|
-
onDisplay={onDisplay}
|
|
1623
|
-
displayAction={entry.displayAction}
|
|
1624
|
-
>
|
|
1625
|
-
{navWrap(entry.render(finalChatRef, renderOpts))}
|
|
1626
|
-
</ChatCardWithDiscuss>
|
|
1627
|
-
)
|
|
1706
|
+
return finish(entry.render(finalChatRef, renderOpts))
|
|
1628
1707
|
}
|
|
1629
1708
|
if (isLoading) return <>{entry.skeleton()}</>
|
|
1630
|
-
if (!item)
|
|
1631
|
-
|
|
1632
|
-
|
|
1709
|
+
if (!item) {
|
|
1710
|
+
// FETCH FAILURE (non-OK response — auth blip, 5xx, rate limit) is
|
|
1711
|
+
// NOT evidence of deletion: render nothing rather than a false
|
|
1712
|
+
// "deleted" claim. Only a SUCCESSFUL fetch that lacks the id
|
|
1713
|
+
// reaches the tombstone below.
|
|
1714
|
+
if (isError) return null
|
|
1715
|
+
// NEVER-FETCHED (query disabled — no list URL registered for this
|
|
1716
|
+
// type, or an empty id): no request was made, so absence of `item`
|
|
1717
|
+
// proves nothing. Render nothing rather than a false "deleted".
|
|
1718
|
+
if (!isFetched) return null
|
|
1719
|
+
// FETCH MISS. Two distinct cases, split by ref provenance:
|
|
1720
|
+
//
|
|
1721
|
+
// - SERVER-BUILT ref (`sourceRepo` present — the entity provably
|
|
1722
|
+
// existed when the answer was written, its ref was persisted
|
|
1723
|
+
// with the message): the entity is GONE now (deleted /
|
|
1724
|
+
// tombstoned). Render the generic deleted-data TOMBSTONE so the
|
|
1725
|
+
// thread keeps its integrity instead of a silent gap — works
|
|
1726
|
+
// for ANY fetch-mode entity type, no per-type wiring.
|
|
1727
|
+
//
|
|
1728
|
+
// - SYNTHETIC client-built ref (bare marker with no refs entry —
|
|
1729
|
+
// possibly a hallucinated id): keep the existing hide-it
|
|
1730
|
+
// behavior; a "deleted" placeholder would assert an existence
|
|
1731
|
+
// we can't vouch for.
|
|
1732
|
+
if (finalChatRef.sourceRepo) {
|
|
1733
|
+
return (
|
|
1734
|
+
<DeletedDataCard
|
|
1735
|
+
title={finalChatRef.title ?? null}
|
|
1736
|
+
entityLabel={entry.label}
|
|
1737
|
+
recoveryNote={
|
|
1738
|
+
(finalChatRef.metadata as Record<string, unknown> | null | undefined)
|
|
1739
|
+
?.recovery_note as string | undefined ?? null
|
|
1740
|
+
}
|
|
1741
|
+
/>
|
|
1742
|
+
)
|
|
1743
|
+
}
|
|
1744
|
+
return null
|
|
1633
1745
|
}
|
|
1634
|
-
return (
|
|
1635
|
-
<ChatCardWithDiscuss
|
|
1636
|
-
chatRef={finalChatRef}
|
|
1637
|
-
onDiscuss={onDiscuss}
|
|
1638
|
-
onDisplay={onDisplay}
|
|
1639
|
-
displayAction={entry.displayAction}
|
|
1640
|
-
>
|
|
1641
|
-
{navWrap(entry.render(item, finalChatRef, renderOpts))}
|
|
1642
|
-
</ChatCardWithDiscuss>
|
|
1643
|
-
)
|
|
1746
|
+
return finish(entry.render(item, finalChatRef, renderOpts))
|
|
1644
1747
|
}
|
|
1645
1748
|
|
|
1646
1749
|
// =============================================================================
|
|
@@ -250,7 +250,7 @@ describe('SSE wire decode — golden fixtures (full hook path)', () => {
|
|
|
250
250
|
vi.stubGlobal('fetch', fetchMock)
|
|
251
251
|
|
|
252
252
|
const { result } = renderAdapter()
|
|
253
|
-
let sendPromise!: Promise<void>
|
|
253
|
+
let sendPromise!: Promise<void | boolean>
|
|
254
254
|
act(() => {
|
|
255
255
|
sendPromise = result.current.sendMessage('will be aborted')
|
|
256
256
|
})
|
|
@@ -28,6 +28,9 @@ export interface UseChatCardItemResult<T = unknown> {
|
|
|
28
28
|
item: T | undefined
|
|
29
29
|
isLoading: boolean
|
|
30
30
|
isError: boolean
|
|
31
|
+
/** True only after a fetch actually completed — false for disabled
|
|
32
|
+
* queries (no list URL for the type / empty id). */
|
|
33
|
+
isFetched: boolean
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
// `extractItems` / `extractItemId` hoisted to `src/utils/extract-items.ts`
|
|
@@ -53,7 +56,12 @@ export function useChatCardItem<T = unknown>(
|
|
|
53
56
|
// sent no credentials, so list endpoints behind the gateway returned
|
|
54
57
|
// 401 and the card rendered blank.
|
|
55
58
|
const res = await embedAuthedFetch(url)
|
|
56
|
-
|
|
59
|
+
// THROW on non-OK (was `return null`): callers must be able to
|
|
60
|
+
// tell "fetched fine, entity absent" (→ deleted tombstone) from
|
|
61
|
+
// "fetch failed" (401 refresh miss, 5xx, 429 → transient; render
|
|
62
|
+
// nothing, never a false 'deleted' claim). TanStack surfaces the
|
|
63
|
+
// throw as `isError`.
|
|
64
|
+
if (!res.ok) throw new Error(`chat card fetch failed: ${res.status}`)
|
|
57
65
|
const data = await res.json()
|
|
58
66
|
const items = extractItems(data)
|
|
59
67
|
const match = items.find((it) => extractItemId(type, it) === id)
|
|
@@ -79,5 +87,11 @@ export function useChatCardItem<T = unknown>(
|
|
|
79
87
|
item: (query.data ?? undefined) as T | undefined,
|
|
80
88
|
isLoading: query.isLoading,
|
|
81
89
|
isError: query.isError,
|
|
90
|
+
// True only after the query actually completed a fetch. DISABLED
|
|
91
|
+
// queries (no list URL registered for the type, empty id) never
|
|
92
|
+
// fetch — `item` is undefined there as well, and callers must not
|
|
93
|
+
// read that as "fetched fine, entity absent" (tombstone gate in
|
|
94
|
+
// entity-cards/dispatch.tsx).
|
|
95
|
+
isFetched: query.isFetched,
|
|
82
96
|
}
|
|
83
97
|
}
|
|
@@ -56,6 +56,7 @@ import { createChatDialogStore, DEFAULT_DIALOG_SIDE } from '../stream/chat-dialo
|
|
|
56
56
|
import { useChatStreamReducer } from '../stream/use-chat-stream-reducer'
|
|
57
57
|
import { processHistoricalMessagesWithErrors } from '../utils/process-historical-messages'
|
|
58
58
|
import { extractIncompleteTailState } from '../utils/extract-incomplete-message-state'
|
|
59
|
+
import { buildDiscussPrompt } from '../utils/discuss-ref-prompt'
|
|
59
60
|
import type {
|
|
60
61
|
ChunkData,
|
|
61
62
|
FetchChunksFunction,
|
|
@@ -1076,10 +1077,33 @@ export function useNatsChatAdapter(
|
|
|
1076
1077
|
[],
|
|
1077
1078
|
)
|
|
1078
1079
|
|
|
1079
|
-
//
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1080
|
+
// "Ask Mingo" on an inline entity card. The agent transport has NO
|
|
1081
|
+
// entity-id-filtered retrieval (its `ContextItemType` enum covers DEVICE /
|
|
1082
|
+
// SCRIPT / TICKET / ORGANIZATION / USER / KB_ARTICLE / POLICY / QUERY /
|
|
1083
|
+
// SCHEDULED_SCRIPT — no content types), so unlike the SSE path there is no
|
|
1084
|
+
// `commandOverride.entityIdFilter` to send: the prompt carries the type + id
|
|
1085
|
+
// as text and the agent resolves the row itself. Same sentence as guide mode
|
|
1086
|
+
// (shared builder) so the affordance reads identically in both.
|
|
1087
|
+
//
|
|
1088
|
+
// This used to be a no-op, which made the card's "Ask Mingo" menu item dead
|
|
1089
|
+
// in Mingo mode. Swap `includeReference` for a structured filter once the
|
|
1090
|
+
// agent backend accepts one.
|
|
1091
|
+
const discussRef = useCallback(
|
|
1092
|
+
(reference: ChatRef) => {
|
|
1093
|
+
const prompt = buildDiscussPrompt(reference, { includeReference: true })
|
|
1094
|
+
// Menu-item click, so there is no composer promise for the rejection to
|
|
1095
|
+
// surface through — a bare `void` would leave it unhandled. Logged in the
|
|
1096
|
+
// same shape as `stopMessage`'s. NOTE: recovering the optimistic rows and
|
|
1097
|
+
// the stuck `thinking` phase after a failed publish is a `sendMessage`
|
|
1098
|
+
// concern (it affects composer sends identically) and is not done here.
|
|
1099
|
+
void sendMessage(prompt).catch((err) => {
|
|
1100
|
+
console.error('[useNatsChatAdapter] discussRef send failed:', err)
|
|
1101
|
+
})
|
|
1102
|
+
},
|
|
1103
|
+
[sendMessage],
|
|
1104
|
+
)
|
|
1105
|
+
// Display stays a no-op: it dispatches a `/… display "<x>"` slash command,
|
|
1106
|
+
// and the agent transport has no slash-command registry.
|
|
1083
1107
|
const displayRef = useCallback((_ref: ChatRef) => {
|
|
1084
1108
|
/* no-op in Mingo mode */
|
|
1085
1109
|
}, [])
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
} from '../utils/chat-conversation-storage'
|
|
68
68
|
import { useChatHistoryHydration } from './use-chat-history-hydration'
|
|
69
69
|
import { sanitizeTitleForChat } from '../utils/slash-dispatch-utils'
|
|
70
|
+
import { buildDiscussPrompt } from '../utils/discuss-ref-prompt'
|
|
70
71
|
import { defaultTableIdForDocumentType } from '../utils/source-icons'
|
|
71
72
|
import type { WireCommandOverride } from '../utils/slash-dispatch-utils'
|
|
72
73
|
import type { ChatAttachment } from '../utils/chat-attachment-markdown'
|
|
@@ -248,23 +249,26 @@ export function useSseChatAdapter(
|
|
|
248
249
|
// creation-time callbacks stay stable.
|
|
249
250
|
|
|
250
251
|
const sendMessageRef = useRef<
|
|
251
|
-
(text: string, options?: InternalSendMessageOptions) => Promise<
|
|
252
|
-
>(async () =>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
252
|
+
(text: string, options?: InternalSendMessageOptions) => Promise<boolean>
|
|
253
|
+
>(async () => true)
|
|
254
|
+
|
|
255
|
+
// Approve/reject pass the transport's boolean outcome through so batch
|
|
256
|
+
// approve-all loops can mark a FAILED row (expired proposal, network
|
|
257
|
+
// error) instead of leaving its execution loader spinning. ONE factory
|
|
258
|
+
// — the two handlers differ only in the action literal.
|
|
259
|
+
const cardResolve = useCallback(
|
|
260
|
+
(action: 'approve' | 'reject') =>
|
|
261
|
+
(reqId?: string): void | Promise<boolean> => {
|
|
262
|
+
if (!reqId) return
|
|
263
|
+
return sendMessageRef.current('', {
|
|
264
|
+
hidden: true,
|
|
265
|
+
approvalAction: { proposalId: reqId, action },
|
|
266
|
+
})
|
|
267
|
+
},
|
|
268
|
+
[],
|
|
269
|
+
)
|
|
270
|
+
const cardApprove = useMemo(() => cardResolve('approve'), [cardResolve])
|
|
271
|
+
const cardReject = useMemo(() => cardResolve('reject'), [cardResolve])
|
|
268
272
|
|
|
269
273
|
const createReducerOptions = useCallback(
|
|
270
274
|
(): ChatStreamReducerOptions => ({
|
|
@@ -415,7 +419,9 @@ export function useSseChatAdapter(
|
|
|
415
419
|
)
|
|
416
420
|
|
|
417
421
|
const sendMessage = useCallback(
|
|
418
|
-
|
|
422
|
+
// Resolves `true` on a clean turn, `false` when the request failed
|
|
423
|
+
// (surfaced to batch approve-all loops via the card callbacks).
|
|
424
|
+
async (text: string, sendOptions?: InternalSendMessageOptions): Promise<boolean> => {
|
|
419
425
|
const { hidden, attachments, commandOverride, approvalAction } = sendOptions ?? {}
|
|
420
426
|
|
|
421
427
|
// URL + body branch — approvalAction routes to the approval-tool
|
|
@@ -466,7 +472,20 @@ export function useSseChatAdapter(
|
|
|
466
472
|
signal: ctrl.signal,
|
|
467
473
|
})
|
|
468
474
|
if (!response.ok) {
|
|
469
|
-
|
|
475
|
+
// Surface the SERVER's error copy (route-base envelope
|
|
476
|
+
// `{error, code}`) — a bare "Chat request failed: 409" told
|
|
477
|
+
// the user nothing when e.g. a batch approval expired; the
|
|
478
|
+
// server ships real copy ("This approval expired — ...").
|
|
479
|
+
let serverMessage: string | null = null
|
|
480
|
+
try {
|
|
481
|
+
const errBody = (await response.json()) as { error?: unknown }
|
|
482
|
+
if (typeof errBody?.error === 'string' && errBody.error.length > 0) {
|
|
483
|
+
serverMessage = errBody.error
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
/* non-JSON error body — fall through to the generic copy */
|
|
487
|
+
}
|
|
488
|
+
throw new Error(serverMessage ?? `Chat request failed: ${response.status}`)
|
|
470
489
|
}
|
|
471
490
|
const reader = response.body?.getReader()
|
|
472
491
|
if (!reader) throw new Error('No response body')
|
|
@@ -496,19 +515,24 @@ export function useSseChatAdapter(
|
|
|
496
515
|
for (const event of endEvents) applyEvent(event)
|
|
497
516
|
}
|
|
498
517
|
}
|
|
518
|
+
return true
|
|
499
519
|
} catch (err) {
|
|
500
520
|
// AbortError on user-initiated stop is expected — keep the partial
|
|
501
|
-
// message, no error row
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
err instanceof Error
|
|
507
|
-
? err.message
|
|
508
|
-
: 'An error occurred while processing your request.',
|
|
509
|
-
),
|
|
510
|
-
)
|
|
521
|
+
// message, no error row, and DON'T report failure (`true`): the
|
|
522
|
+
// request may have committed server-side, and a `false` here
|
|
523
|
+
// would falsely tick a batch row's failure cross.
|
|
524
|
+
if ((err as { name?: string })?.name === 'AbortError' || ctrl.signal.aborted) {
|
|
525
|
+
return true
|
|
511
526
|
}
|
|
527
|
+
flushDeltas()
|
|
528
|
+
mutate((r) =>
|
|
529
|
+
r.failSseTurn(
|
|
530
|
+
err instanceof Error
|
|
531
|
+
? err.message
|
|
532
|
+
: 'An error occurred while processing your request.',
|
|
533
|
+
),
|
|
534
|
+
)
|
|
535
|
+
return false
|
|
512
536
|
} finally {
|
|
513
537
|
if (abortControllerRef.current === ctrl) {
|
|
514
538
|
abortControllerRef.current = null
|
|
@@ -657,9 +681,10 @@ export function useSseChatAdapter(
|
|
|
657
681
|
}
|
|
658
682
|
// RETRIEVAL IS STRICTLY PRIMARY-KEY-DRIVEN. The visible prose
|
|
659
683
|
// ("Tell me more about <title>") is UX-only; retrieval narrows
|
|
660
|
-
// via `entityIdFilter` before the LLM is invoked.
|
|
661
|
-
|
|
662
|
-
|
|
684
|
+
// via `entityIdFilter` before the LLM is invoked. The sentence itself
|
|
685
|
+
// comes from the shared builder so the Mingo transport (which has no
|
|
686
|
+
// id filter and leans on the prose) phrases it identically.
|
|
687
|
+
const prompt = buildDiscussPrompt(reference)
|
|
663
688
|
sendMessage(prompt, {
|
|
664
689
|
commandOverride: { entityIdFilter: { tableId, id: refId } },
|
|
665
690
|
})
|