@flamingo-stack/openframe-frontend-core 0.0.222 → 0.0.223-snapshot.20260604201541
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-QYRV6MKX.js → chunk-53EA4QPZ.js} +13 -7
- package/dist/chunk-53EA4QPZ.js.map +1 -0
- package/dist/{chunk-GMNE7P7V.cjs → chunk-7JGR67H2.cjs} +26 -26
- package/dist/{chunk-GMNE7P7V.cjs.map → chunk-7JGR67H2.cjs.map} +1 -1
- package/dist/{chunk-KM4KK2A6.cjs → chunk-AENTBOSN.cjs} +2656 -1774
- package/dist/chunk-AENTBOSN.cjs.map +1 -0
- package/dist/{chunk-AIKPR6BE.js → chunk-BNYPQCKL.js} +6179 -5297
- package/dist/chunk-BNYPQCKL.js.map +1 -0
- package/dist/{chunk-QNYH3WUU.cjs → chunk-JUWPBMQM.cjs} +13 -7
- package/dist/chunk-JUWPBMQM.cjs.map +1 -0
- package/dist/{chunk-CCSEXVI4.js → chunk-LEHEHEVW.js} +2 -2
- package/dist/{chunk-ND24XSVT.js → chunk-OAOLVEYW.js} +3 -3
- package/dist/chunk-OAOLVEYW.js.map +1 -0
- package/dist/{chunk-4JOWZLEL.cjs → chunk-SZCTY3YF.cjs} +7 -7
- package/dist/{chunk-4JOWZLEL.cjs.map → chunk-SZCTY3YF.cjs.map} +1 -1
- package/dist/{chunk-RMOHKAHA.js → chunk-V6ME2VMU.js} +3 -3
- package/dist/{chunk-IZA5WF44.cjs → chunk-WIAQRCJ5.cjs} +12 -12
- package/dist/{chunk-IZA5WF44.cjs.map → chunk-WIAQRCJ5.cjs.map} +1 -1
- package/dist/{chunk-HRIAZQ5D.js → chunk-YEOM52ZR.js} +3 -3
- package/dist/{chunk-A2SFHYVF.cjs → chunk-ZCHTRQBO.cjs} +10 -10
- package/dist/{chunk-A2SFHYVF.cjs.map → chunk-ZCHTRQBO.cjs.map} +1 -1
- package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
- package/dist/components/chat/chat-composer.d.ts.map +1 -1
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-skeleton.d.ts +19 -4
- package/dist/components/chat/chat-message-skeleton.d.ts.map +1 -1
- package/dist/components/chat/chat-panel-header-mobile.d.ts +24 -0
- package/dist/components/chat/chat-panel-header-mobile.d.ts.map +1 -0
- package/dist/components/chat/chat-panel-header.d.ts +6 -0
- package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
- package/dist/components/chat/chat-quick-action-row.d.ts +31 -0
- package/dist/components/chat/chat-quick-action-row.d.ts.map +1 -0
- package/dist/components/chat/embeddable-chat.d.ts +27 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/dispatch.d.ts +14 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/github-activity-card.d.ts +10 -1
- package/dist/components/chat/entity-cards/github-activity-card.d.ts.map +1 -1
- package/dist/components/chat/guide-welcome.d.ts.map +1 -1
- 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 +9 -0
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-slash-commands.d.ts +26 -0
- package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +12 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-unified-chat.d.ts +12 -0
- package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +9 -3
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +8 -2
- package/dist/components/chat/mingo-chat-history.d.ts +10 -0
- package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
- package/dist/components/chat/mingo-info-card.d.ts +63 -0
- package/dist/components/chat/mingo-info-card.d.ts.map +1 -0
- package/dist/components/chat/mingo-welcome.d.ts +14 -1
- package/dist/components/chat/mingo-welcome.d.ts.map +1 -1
- package/dist/components/chat/types/chat.types.d.ts +6 -0
- package/dist/components/chat/types/chat.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +40 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/index.cjs +110 -104
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +36 -30
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/app-layout-drawer.d.ts +7 -0
- package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/navigation/index.js +2 -2
- package/dist/components/onboarding-guides/index.cjs +26 -26
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/tickets/index.cjs +67 -67
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/actions-menu.d.ts +9 -0
- package/dist/components/ui/actions-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-button.d.ts +2 -2
- package/dist/components/ui/dropdown-button.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +9 -3
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +8 -2
- package/dist/components/ui/modal-v2.d.ts.map +1 -1
- package/dist/components/ui/more-actions-menu.d.ts +13 -0
- package/dist/components/ui/more-actions-menu.d.ts.map +1 -1
- package/dist/components/ui/simple-markdown-renderer.d.ts +9 -1
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
- package/dist/components/ui/square-avatar.d.ts +4 -0
- package/dist/components/ui/square-avatar.d.ts.map +1 -1
- package/dist/components/ui/textarea.d.ts.map +1 -1
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/ui/use-auto-limit-tags.d.ts +7 -1
- package/dist/hooks/ui/use-auto-limit-tags.d.ts.map +1 -1
- package/dist/index.cjs +9 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -2
- package/package.json +1 -1
- package/src/components/chat/chat-archive-page.tsx +32 -7
- package/src/components/chat/chat-composer.tsx +6 -2
- package/src/components/chat/chat-message-enhanced.tsx +91 -21
- package/src/components/chat/chat-message-list.tsx +1 -1
- package/src/components/chat/chat-message-skeleton.tsx +60 -60
- package/src/components/chat/chat-panel-header-mobile.tsx +94 -0
- package/src/components/chat/chat-panel-header.tsx +78 -50
- package/src/components/chat/chat-quick-action-row.tsx +146 -0
- package/src/components/chat/embeddable-chat.tsx +185 -52
- package/src/components/chat/entity-cards/dispatch.tsx +770 -232
- package/src/components/chat/entity-cards/github-activity-card.tsx +9 -3
- package/src/components/chat/guide-welcome.tsx +11 -8
- package/src/components/chat/hooks/use-chat-card-item.ts +17 -1
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +80 -7
- package/src/components/chat/hooks/use-slash-commands.ts +39 -0
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +49 -45
- package/src/components/chat/hooks/use-unified-chat.ts +69 -34
- package/src/components/chat/mingo-chat-history.tsx +118 -36
- package/src/components/chat/mingo-info-card.tsx +214 -0
- package/src/components/chat/mingo-welcome.tsx +89 -36
- package/src/components/chat/types/chat.types.ts +4 -0
- package/src/components/chat/types/unified-chat-state.types.ts +46 -0
- package/src/components/chat/utils/process-historical-messages.ts +8 -0
- package/src/components/content-loading-container.tsx +1 -1
- package/src/components/empty-state.tsx +3 -3
- package/src/components/faq-accordion.tsx +2 -2
- package/src/components/features/changelog-manager.tsx +2 -2
- package/src/components/features/highlight-config-section.tsx +2 -2
- package/src/components/features/og-editor-preview.tsx +3 -3
- package/src/components/features/release-media-manager.tsx +2 -2
- package/src/components/loading/card-skeleton.tsx +3 -3
- package/src/components/loading/device-card-skeleton.tsx +1 -1
- package/src/components/loading/margin-report-skeleton.tsx +2 -2
- package/src/components/loading/organization-card-skeleton.tsx +1 -1
- package/src/components/loading/organization-icon-skeleton.tsx +1 -1
- package/src/components/loading/page-layout-skeleton.tsx +9 -9
- package/src/components/loading/users-grid-skeleton.tsx +2 -2
- package/src/components/logs-list.tsx +1 -1
- package/src/components/media-carousel.tsx +1 -1
- package/src/components/navigation/app-layout-drawer.tsx +70 -6
- package/src/components/open-source-features.tsx +2 -2
- package/src/components/ui/actions-menu.tsx +36 -13
- package/src/components/ui/allowed-domains-input.tsx +3 -3
- package/src/components/ui/cursor-pagination.tsx +1 -1
- package/src/components/ui/dropdown-button.tsx +6 -6
- package/src/components/ui/info-row.tsx +2 -2
- package/src/components/ui/modal-v2.tsx +8 -1
- package/src/components/ui/more-actions-menu.tsx +13 -0
- package/src/components/ui/simple-markdown-renderer.tsx +12 -2
- package/src/components/ui/square-avatar.tsx +6 -1
- package/src/components/ui/tags-input.tsx +1 -1
- package/src/components/ui/textarea.tsx +18 -4
- package/src/components/vendor-icon.tsx +1 -1
- package/src/components/vendor-tag.tsx +1 -1
- package/src/components/why-it-matters.tsx +1 -1
- package/src/hooks/ui/use-auto-limit-tags.ts +22 -8
- package/src/stories/ActionsMenu.stories.tsx +256 -0
- package/src/stories/AppLayoutSidebar.stories.tsx +6 -0
- package/src/stories/Board.stories.tsx +1 -1
- package/src/stories/ChatMingoInfoCards.stories.tsx +748 -0
- package/src/stories/ChatTicketList.stories.tsx +1 -1
- package/src/stories/DataTable.stories.tsx +17 -13
- package/src/stories/EmbeddableChat.stories.tsx +233 -1
- package/src/stories/MingoInfoCard.stories.tsx +193 -0
- package/src/stories/MingoOnboardingCard.stories.tsx +1 -1
- package/src/stories/Modal.stories.tsx +1 -0
- package/src/stories/NotificationDrawer.stories.tsx +14 -3
- package/src/stories/PageActions.stories.tsx +4 -4
- package/src/stories/PageContainer.stories.tsx +5 -5
- package/src/stories/PageLayout.stories.tsx +1 -1
- package/src/stories/TabSelector.stories.tsx +2 -2
- package/src/stories/Tag.stories.tsx +10 -5
- package/src/stories/TicketInfoSection.stories.tsx +23 -11
- package/src/stories/Toaster.stories.tsx +5 -5
- package/src/stories/__fixtures__/chat-card-decorator.tsx +44 -0
- package/dist/chunk-AIKPR6BE.js.map +0 -1
- package/dist/chunk-KM4KK2A6.cjs.map +0 -1
- package/dist/chunk-ND24XSVT.js.map +0 -1
- package/dist/chunk-QNYH3WUU.cjs.map +0 -1
- package/dist/chunk-QYRV6MKX.js.map +0 -1
- package/src/stories/.TicketDescriptionViewer.stories.md +0 -39
- package/src/stories/.TicketKnowledgeBaseList.stories.md +0 -36
- package/src/stories/TicketDescriptionViewer.stories.tsx +0 -70
- package/src/stories/TicketKnowledgeBaseList.stories.tsx +0 -51
- /package/dist/{chunk-CCSEXVI4.js.map → chunk-LEHEHEVW.js.map} +0 -0
- /package/dist/{chunk-RMOHKAHA.js.map → chunk-V6ME2VMU.js.map} +0 -0
- /package/dist/{chunk-HRIAZQ5D.js.map → chunk-YEOM52ZR.js.map} +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { cn } from '../../utils/cn'
|
|
5
|
+
import { Tag } from '../ui/tag'
|
|
6
|
+
import { ActionsMenuDropdown } from '../ui/actions-menu'
|
|
7
|
+
import { Ellipsis01Icon } from '../icons-v2-generated'
|
|
8
|
+
import { useAutoLimitTags } from '../../hooks/ui/use-auto-limit-tags'
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/** A single collapsible quick-action chip. */
|
|
15
|
+
export interface QuickActionChip {
|
|
16
|
+
/** Stable React key + menu-item id. */
|
|
17
|
+
id: string
|
|
18
|
+
label: string
|
|
19
|
+
icon?: React.ReactNode
|
|
20
|
+
/** `'primary'` = accent (yellow) chip, `'outline'` = bordered chip (default). */
|
|
21
|
+
variant?: 'primary' | 'outline'
|
|
22
|
+
onSelect?: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ChatQuickActionRowProps {
|
|
26
|
+
/** Always-visible leading chip(s) (e.g. Mingo's "Start Guide Chat"). Never
|
|
27
|
+
* collapses into the overflow menu — rendered before the measured zone. */
|
|
28
|
+
leading?: React.ReactNode
|
|
29
|
+
/** Collapsible chips — as many as fit on one line render inline; the rest
|
|
30
|
+
* collapse under a trailing "⋯" overflow menu (small icon button). */
|
|
31
|
+
chips: ReadonlyArray<QuickActionChip>
|
|
32
|
+
className?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Chip button
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
function ChipButton({ chip }: { chip: QuickActionChip }) {
|
|
40
|
+
return (
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
onClick={chip.onSelect}
|
|
44
|
+
className="shrink-0 rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
|
|
45
|
+
>
|
|
46
|
+
<Tag variant={chip.variant ?? 'outline'} icon={chip.icon} label={chip.label} />
|
|
47
|
+
</button>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// Component
|
|
53
|
+
// =============================================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A single-line quick-action chip row that collapses overflow into a "⋯" menu.
|
|
57
|
+
*
|
|
58
|
+
* Same dynamic, width-measured overflow used by the multi-select Autocomplete
|
|
59
|
+
* (`useAutoLimitTags`): off-screen copies of every chip are measured, as many
|
|
60
|
+
* as fit render inline, and the remainder move into an `ActionsMenuDropdown`
|
|
61
|
+
* behind a small "⋯" icon button. An optional `leading` slot stays pinned and
|
|
62
|
+
* never collapses, so the row's primary action is always reachable.
|
|
63
|
+
*/
|
|
64
|
+
export function ChatQuickActionRow({
|
|
65
|
+
leading,
|
|
66
|
+
chips,
|
|
67
|
+
className,
|
|
68
|
+
}: ChatQuickActionRowProps) {
|
|
69
|
+
const auto = useAutoLimitTags({
|
|
70
|
+
count: chips.length,
|
|
71
|
+
reserveInputWidth: false,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const visibleChips = chips.slice(0, auto.visibleCount)
|
|
75
|
+
const hiddenChips = chips.slice(auto.visibleCount)
|
|
76
|
+
|
|
77
|
+
if (!leading && chips.length === 0) return null
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
className={cn('relative flex shrink-0 items-center gap-1', className)}
|
|
82
|
+
>
|
|
83
|
+
{leading}
|
|
84
|
+
|
|
85
|
+
{chips.length > 0 && (
|
|
86
|
+
// Measured zone: `flex-1 min-w-0 overflow-hidden` so it claims the width
|
|
87
|
+
// left by the pinned `leading` slot and clips anything past the fit.
|
|
88
|
+
<div
|
|
89
|
+
ref={auto.middleRef}
|
|
90
|
+
className="flex flex-1 min-w-0 items-center gap-1 overflow-hidden"
|
|
91
|
+
>
|
|
92
|
+
{visibleChips.map((chip) => (
|
|
93
|
+
<ChipButton key={chip.id} chip={chip} />
|
|
94
|
+
))}
|
|
95
|
+
|
|
96
|
+
{hiddenChips.length > 0 && (
|
|
97
|
+
<ActionsMenuDropdown
|
|
98
|
+
triggerAriaLabel="More quick actions"
|
|
99
|
+
// Don't return focus (and its ring) to the "⋯" trigger on close.
|
|
100
|
+
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
101
|
+
groups={[
|
|
102
|
+
{
|
|
103
|
+
items: hiddenChips.map((chip) => ({
|
|
104
|
+
id: chip.id,
|
|
105
|
+
label: chip.label,
|
|
106
|
+
icon: chip.icon,
|
|
107
|
+
onClick: chip.onSelect,
|
|
108
|
+
})),
|
|
109
|
+
},
|
|
110
|
+
]}
|
|
111
|
+
customTrigger={
|
|
112
|
+
<button
|
|
113
|
+
ref={auto.badgeRef}
|
|
114
|
+
type="button"
|
|
115
|
+
aria-label="More quick actions"
|
|
116
|
+
className="shrink-0 rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
|
|
117
|
+
>
|
|
118
|
+
<Tag variant="outline" label={<Ellipsis01Icon size={16} />} />
|
|
119
|
+
</button>
|
|
120
|
+
}
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{/* Off-screen measurement copies of every chip — the hook reads their
|
|
127
|
+
widths to decide how many fit. Mirror the visible chip markup (same
|
|
128
|
+
variant/icon/label and the same `gap-1`) so the measured widths match
|
|
129
|
+
what actually renders. */}
|
|
130
|
+
<div
|
|
131
|
+
ref={auto.measureRef}
|
|
132
|
+
aria-hidden
|
|
133
|
+
className="pointer-events-none invisible absolute left-0 top-0 -z-10 flex gap-1"
|
|
134
|
+
>
|
|
135
|
+
{chips.map((chip) => (
|
|
136
|
+
<Tag
|
|
137
|
+
key={`measure-${chip.id}`}
|
|
138
|
+
variant={chip.variant ?? 'outline'}
|
|
139
|
+
icon={chip.icon}
|
|
140
|
+
label={chip.label}
|
|
141
|
+
/>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
@@ -56,6 +56,7 @@ import { ChatComposer } from './chat-composer'
|
|
|
56
56
|
import { useChatDialogManager } from './hooks/use-chat-dialog-manager'
|
|
57
57
|
import { ChatDialogModals } from './mingo-chat-modals'
|
|
58
58
|
import { ChatMessageList } from './chat-message-list'
|
|
59
|
+
import { ChatMessageListSkeleton } from './chat-message-skeleton'
|
|
59
60
|
import { SourceActionButton } from './source-action-button'
|
|
60
61
|
import { NavLinkAnchorViaRuntime } from './nav-link-anchor-via-runtime'
|
|
61
62
|
import { ChatAttachmentChipStrip } from './chat-attachment-bar'
|
|
@@ -70,6 +71,7 @@ import {
|
|
|
70
71
|
type ChatMode,
|
|
71
72
|
type UseUnifiedChatModes,
|
|
72
73
|
} from './hooks/use-unified-chat'
|
|
74
|
+
import type { UnifiedChatState } from './types/unified-chat-state.types'
|
|
73
75
|
import type {
|
|
74
76
|
UseNatsChatAdapterConfig,
|
|
75
77
|
FetchDialogsParams,
|
|
@@ -79,7 +81,7 @@ import { useChatAttachments } from './hooks/use-chat-attachments'
|
|
|
79
81
|
import { useChatAttachmentImageGallery } from './hooks/use-chat-attachment-image-gallery'
|
|
80
82
|
import { useChatIdentity } from './hooks/use-chat-identity'
|
|
81
83
|
import { useCloseOnNavigation } from './hooks/use-close-on-navigation'
|
|
82
|
-
import { fetchSlashCommands, type SlashCommandSummary } from './hooks/use-slash-commands'
|
|
84
|
+
import { fetchSlashCommands, useSlashCommandRegistry, type SlashCommandSummary } from './hooks/use-slash-commands'
|
|
83
85
|
|
|
84
86
|
import type { ChatRef } from './chat-ref.types'
|
|
85
87
|
import type { ChatInputRef, SlashCommandActionId } from './types/component.types'
|
|
@@ -162,6 +164,35 @@ export interface EmbeddableChatProps {
|
|
|
162
164
|
*/
|
|
163
165
|
modes?: UseUnifiedChatModes
|
|
164
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Pre-built Mingo-mode state, supplied by the host instead of letting the
|
|
169
|
+
* built-in NATS adapter own it. When provided, the panel renders Mingo mode
|
|
170
|
+
* from this object and opens no subscription of its own — the host keeps
|
|
171
|
+
* chat data + streaming in its own store/cache so it survives the panel
|
|
172
|
+
* unmounting (no `keepMounted` needed). The host should then NOT pass
|
|
173
|
+
* `modes.mingo` (Guide-mode wiring via `modes.guide` is unaffected).
|
|
174
|
+
*/
|
|
175
|
+
mingoState?: UnifiedChatState
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Dialog-management capabilities for injected Mingo mode (`mingoState`).
|
|
179
|
+
*
|
|
180
|
+
* When the host injects `mingoState`, it doesn't pass `modes.mingo` (that
|
|
181
|
+
* would re-activate the idle built-in adapter), so the rename/archive/
|
|
182
|
+
* restore/archive-page affordances can't read their capability flags off the
|
|
183
|
+
* callback config. Supply them here instead. `canRename`/`canArchive` default
|
|
184
|
+
* to `true` when `mingoState` is set; the archive page + restore are shown
|
|
185
|
+
* only when their callbacks are provided. Ignored unless `mingoState` is set.
|
|
186
|
+
*/
|
|
187
|
+
mingoDialogCapabilities?: {
|
|
188
|
+
canRename?: boolean
|
|
189
|
+
canArchive?: boolean
|
|
190
|
+
fetchArchivedDialogs?: (
|
|
191
|
+
params: FetchDialogsParams,
|
|
192
|
+
) => Promise<FetchDialogsResult>
|
|
193
|
+
unarchiveDialog?: (id: string) => Promise<void>
|
|
194
|
+
}
|
|
195
|
+
|
|
165
196
|
/**
|
|
166
197
|
* Controlled active-mode. When provided, `onActiveModeChange` MUST
|
|
167
198
|
* also be provided. For uncontrolled use see `defaultActiveMode`.
|
|
@@ -605,6 +636,8 @@ function EmbeddableChatInner({
|
|
|
605
636
|
extras,
|
|
606
637
|
tableIdForDocumentType,
|
|
607
638
|
modes,
|
|
639
|
+
mingoState,
|
|
640
|
+
mingoDialogCapabilities,
|
|
608
641
|
activeMode: controlledActiveMode,
|
|
609
642
|
onActiveModeChange,
|
|
610
643
|
defaultActiveMode,
|
|
@@ -698,35 +731,9 @@ function EmbeddableChatInner({
|
|
|
698
731
|
// Imperative handle to the chat input.
|
|
699
732
|
const chatInputRef = useRef<ChatInputRef | null>(null)
|
|
700
733
|
|
|
701
|
-
//
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
)
|
|
705
|
-
const [commandsLoaded, setCommandsLoaded] = useState(false)
|
|
706
|
-
useEffect(() => {
|
|
707
|
-
if (!isOpen) return
|
|
708
|
-
let cancelled = false
|
|
709
|
-
const ctrl = new AbortController()
|
|
710
|
-
setCommandsLoaded(false)
|
|
711
|
-
fetchSlashCommands('', ctrl.signal, commandsUrl)
|
|
712
|
-
.then((commands) => {
|
|
713
|
-
if (cancelled) return
|
|
714
|
-
const map = new Map<string, SlashCommandSummary>()
|
|
715
|
-
for (const cmd of commands) map.set(cmd.id, cmd)
|
|
716
|
-
setCommandsById(map)
|
|
717
|
-
setCommandsLoaded(true)
|
|
718
|
-
})
|
|
719
|
-
.catch((err) => {
|
|
720
|
-
if (!cancelled && (err as Error)?.name !== 'AbortError') {
|
|
721
|
-
console.warn('[embeddable-chat] failed to fetch slash commands:', err)
|
|
722
|
-
setCommandsLoaded(true)
|
|
723
|
-
}
|
|
724
|
-
})
|
|
725
|
-
return () => {
|
|
726
|
-
cancelled = true
|
|
727
|
-
ctrl.abort()
|
|
728
|
-
}
|
|
729
|
-
}, [isOpen, source, commandsUrl])
|
|
734
|
+
// The slash-command registry (Guide onboarding cards) is loaded via
|
|
735
|
+
// react-query below, after `activeMode` is resolved — gated on Guide mode and
|
|
736
|
+
// cached across remounts. See `commandsById` / `commandsLoaded`.
|
|
730
737
|
|
|
731
738
|
// Slash-command autocomplete config — passed to <ChatInput>.
|
|
732
739
|
const slashCommandsProp = useMemo(
|
|
@@ -774,6 +781,36 @@ function EmbeddableChatInner({
|
|
|
774
781
|
return { guide: guideOptions }
|
|
775
782
|
}, [modes, tableIdForDocumentType])
|
|
776
783
|
|
|
784
|
+
// Resolve dialog-management capabilities (rename / archive / archive-page /
|
|
785
|
+
// restore) from a single source. With injected `mingoState` they come from
|
|
786
|
+
// `mingoDialogCapabilities` (the host doesn't pass `modes.mingo`); otherwise
|
|
787
|
+
// from the callback-config shape, where the presence of a callback IS the
|
|
788
|
+
// capability. Both feed the same gating below so the JSX has one source.
|
|
789
|
+
const mingoCaps = useMemo(() => {
|
|
790
|
+
if (mingoState) {
|
|
791
|
+
// Default OFF: the row ⋯ menu (Rename / Archive) and the archive page are
|
|
792
|
+
// shown only when the host explicitly opts in — same capability-gating as
|
|
793
|
+
// the archive button (`fetchArchivedDialogs` presence). Otherwise the menu
|
|
794
|
+
// would advertise actions the host hasn't actually wired (no-ops).
|
|
795
|
+
return {
|
|
796
|
+
canRename: mingoDialogCapabilities?.canRename ?? false,
|
|
797
|
+
canArchive: mingoDialogCapabilities?.canArchive ?? false,
|
|
798
|
+
fetchArchivedDialogs: mingoDialogCapabilities?.fetchArchivedDialogs,
|
|
799
|
+
unarchiveDialog: mingoDialogCapabilities?.unarchiveDialog,
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
canRename: !!effectiveModes.mingo?.renameDialog,
|
|
804
|
+
canArchive: !!effectiveModes.mingo?.archiveDialog,
|
|
805
|
+
fetchArchivedDialogs: effectiveModes.mingo?.fetchArchivedDialogs,
|
|
806
|
+
unarchiveDialog: effectiveModes.mingo?.unarchiveDialog,
|
|
807
|
+
}
|
|
808
|
+
}, [mingoState, mingoDialogCapabilities, effectiveModes])
|
|
809
|
+
|
|
810
|
+
// "Does Mingo mode exist?" — true via either the callback config OR injected
|
|
811
|
+
// state. Gates the guide↔mingo back-chevron and the guide-mode banner.
|
|
812
|
+
const hasMingoMode = !!effectiveModes.mingo || !!mingoState
|
|
813
|
+
|
|
777
814
|
// Initial active mode picks the first configured slot when neither
|
|
778
815
|
// controlled `activeMode` nor `defaultActiveMode` is provided. Guide
|
|
779
816
|
// wins ties so legacy callers see no behaviour change.
|
|
@@ -795,6 +832,22 @@ function EmbeddableChatInner({
|
|
|
795
832
|
[controlledActiveMode, onActiveModeChange],
|
|
796
833
|
)
|
|
797
834
|
|
|
835
|
+
// Slash-command registry (Guide onboarding cards) via the shared
|
|
836
|
+
// `useSlashCommandRegistry` react-query hook:
|
|
837
|
+
// - `enabled: activeMode === 'guide'` → never fetched in Mingo mode, so
|
|
838
|
+
// opening the default Mingo panel doesn't hit the commands endpoint.
|
|
839
|
+
// - Shares one cache entry (keyed on `commandsUrl`) with the SSE adapter's
|
|
840
|
+
// `displayRef` lookup, so Guide mode fetches `commands` ONCE, not twice.
|
|
841
|
+
// - Cached with `staleTime/gcTime: Infinity` inside the hook, so toggling
|
|
842
|
+
// to Guide or reopening the (remounting) drawer reads from cache.
|
|
843
|
+
const { commands: commandsList, loaded: commandsLoaded } =
|
|
844
|
+
useSlashCommandRegistry(commandsUrl, { enabled: activeMode === 'guide' })
|
|
845
|
+
const commandsById = useMemo(() => {
|
|
846
|
+
const map = new Map<string, SlashCommandSummary>()
|
|
847
|
+
for (const cmd of commandsList) map.set(cmd.id, cmd)
|
|
848
|
+
return map
|
|
849
|
+
}, [commandsList])
|
|
850
|
+
|
|
798
851
|
const {
|
|
799
852
|
messages: rawMessages,
|
|
800
853
|
isLoading: chatLoading,
|
|
@@ -817,9 +870,12 @@ function EmbeddableChatInner({
|
|
|
817
870
|
renameDialog,
|
|
818
871
|
archiveDialog,
|
|
819
872
|
isDialogsLoading,
|
|
873
|
+
dialogsError,
|
|
874
|
+
reloadDialogs,
|
|
875
|
+
isMessagesLoading,
|
|
820
876
|
hasMoreDialogs,
|
|
821
877
|
loadMoreDialogs,
|
|
822
|
-
} = useUnifiedChat({ modes: effectiveModes, activeMode })
|
|
878
|
+
} = useUnifiedChat({ modes: effectiveModes, activeMode, mingoStateOverride: mingoState })
|
|
823
879
|
|
|
824
880
|
// Chat-attachment hooks (v2 attachment feature).
|
|
825
881
|
const {
|
|
@@ -878,21 +934,71 @@ function EmbeddableChatInner({
|
|
|
878
934
|
[discussRef, displayRef, resolvedBaseRoute, chipBasePlatform, extras],
|
|
879
935
|
)
|
|
880
936
|
|
|
937
|
+
// Stable assistant-icon element. `<ChatMessageList>` forwards this prop to
|
|
938
|
+
// every assistant `<ChatMessageEnhanced>`, whose memo compares it BY
|
|
939
|
+
// REFERENCE. Created inline in JSX it was a fresh element on every render,
|
|
940
|
+
// defeating the memo for all assistant messages (re-rendering completed
|
|
941
|
+
// bubbles — and re-mounting their inline cards — on every realtime chunk).
|
|
942
|
+
const mingoAssistantIcon = useMemo(
|
|
943
|
+
() => <MingoIcon className="h-6 w-6" cornerColor="var(--ods-flamingo-cyan-base)" />,
|
|
944
|
+
[],
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
// Stable per-message timestamps. The memoized `<ChatMessageEnhanced>`
|
|
948
|
+
// compares `timestamp.getTime()`, so we must NOT stamp `new Date()` fresh
|
|
949
|
+
// on each render: a moving clock makes every message's timestamp differ
|
|
950
|
+
// between renders, defeating memoization and forcing the WHOLE list (and
|
|
951
|
+
// every open menu/card inside it) to re-render on every realtime chunk.
|
|
952
|
+
// Prefer the host's real timestamp; otherwise stamp once per id and reuse
|
|
953
|
+
// it (keeps the displayed time stable and lets memoization hold even when
|
|
954
|
+
// the host omits a timestamp).
|
|
955
|
+
const timestampCacheRef = useRef<Map<string, Date>>(new Map())
|
|
956
|
+
|
|
881
957
|
// Map docMessages → lib's Message type, forwarding chatRefs + scrollAnchor.
|
|
882
|
-
const messages: Message[] = useMemo(
|
|
883
|
-
|
|
884
|
-
|
|
958
|
+
const messages: Message[] = useMemo(() => {
|
|
959
|
+
const cache = timestampCacheRef.current
|
|
960
|
+
const seenIds = new Set<string>()
|
|
961
|
+
|
|
962
|
+
const mapped = rawMessages.map((m) => {
|
|
963
|
+
seenIds.add(m.id)
|
|
964
|
+
let timestamp: Date
|
|
965
|
+
if (m.timestamp != null) {
|
|
966
|
+
timestamp = new Date(m.timestamp)
|
|
967
|
+
} else {
|
|
968
|
+
const cached = cache.get(m.id)
|
|
969
|
+
timestamp = cached ?? new Date()
|
|
970
|
+
if (!cached) cache.set(m.id, timestamp)
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return {
|
|
885
974
|
id: m.id,
|
|
886
975
|
role: m.role as 'user' | 'assistant',
|
|
887
|
-
|
|
976
|
+
// Host-supplied per-message name/avatar win (e.g. the signed-in user's
|
|
977
|
+
// full name + photo on a `user` bubble); fall back to the role default
|
|
978
|
+
// when the host doesn't provide them.
|
|
979
|
+
name: m.name ?? (m.role === 'assistant' ? 'Mingo' : 'You'),
|
|
980
|
+
avatar: m.avatar ?? null,
|
|
981
|
+
// Forward the host's authorType so user bubbles get the same accent
|
|
982
|
+
// name color as the standalone /mingo page (user → 'admin').
|
|
983
|
+
...(m.authorType ? { authorType: m.authorType } : {}),
|
|
888
984
|
content: m.segments && m.segments.length > 0 ? m.segments : m.content,
|
|
889
|
-
timestamp
|
|
985
|
+
timestamp,
|
|
890
986
|
assistantType: m.role === 'assistant' ? ('mingo' as const) : undefined,
|
|
891
987
|
...(m.chatRefs ? { chatRefs: m.chatRefs } : {}),
|
|
892
988
|
...(m.scrollAnchor ? { scrollAnchor: m.scrollAnchor } : {}),
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
|
|
989
|
+
}
|
|
990
|
+
})
|
|
991
|
+
|
|
992
|
+
// Drop cached fallbacks for messages no longer present so the map can't
|
|
993
|
+
// grow unbounded across a long-lived session.
|
|
994
|
+
if (cache.size > seenIds.size) {
|
|
995
|
+
for (const id of cache.keys()) {
|
|
996
|
+
if (!seenIds.has(id)) cache.delete(id)
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
return mapped
|
|
1001
|
+
}, [rawMessages])
|
|
896
1002
|
|
|
897
1003
|
const handleSend = useCallback(
|
|
898
1004
|
(text: string) => {
|
|
@@ -951,8 +1057,8 @@ function EmbeddableChatInner({
|
|
|
951
1057
|
clearMessages,
|
|
952
1058
|
renameDialog,
|
|
953
1059
|
archiveDialog,
|
|
954
|
-
fetchArchivedDialogs:
|
|
955
|
-
unarchiveDialog:
|
|
1060
|
+
fetchArchivedDialogs: mingoCaps.fetchArchivedDialogs,
|
|
1061
|
+
unarchiveDialog: mingoCaps.unarchiveDialog,
|
|
956
1062
|
})
|
|
957
1063
|
|
|
958
1064
|
const handleOpen = useCallback(() => setIsOpen(true), [setIsOpen])
|
|
@@ -983,12 +1089,24 @@ function EmbeddableChatInner({
|
|
|
983
1089
|
}, [source, discussRef, setIsOpen])
|
|
984
1090
|
|
|
985
1091
|
const hasMessages = messages.length > 0
|
|
1092
|
+
// First dialog page in flight and nothing cached yet — we don't yet know if
|
|
1093
|
+
// the user is new or returning, so the Mingo empty state shows a skeleton
|
|
1094
|
+
// rather than flashing the new-user greeting+grid before the list lands.
|
|
1095
|
+
const dialogsInitialLoading = isDialogsLoading && dialogs.length === 0
|
|
1096
|
+
// Dialog list failed to load (e.g. backend down) and we have nothing cached.
|
|
1097
|
+
// Distinct from "no chats" so the Mingo empty state can offer a retry instead
|
|
1098
|
+
// of the new-user greeting (which misleadingly advertises Guide).
|
|
1099
|
+
const dialogsLoadError = dialogsError && dialogs.length === 0
|
|
986
1100
|
// A conversation is "open" the instant a chat is selected (`isOpeningDialog` /
|
|
987
1101
|
// `isViewingArchived` are set synchronously on click) — not only once its
|
|
988
1102
|
// history has loaded (`hasMessages`). Driving the surface + content branch
|
|
989
1103
|
// off this makes the normal-chat open animate identically to the archived
|
|
990
1104
|
// one instead of lagging behind the message fetch.
|
|
991
1105
|
const hasConversation = hasMessages || isOpeningDialog || isViewingArchived
|
|
1106
|
+
// Opening a dialog whose history hasn't arrived yet — show a message-list
|
|
1107
|
+
// skeleton instead of an empty thread so the open reads as "loading" rather
|
|
1108
|
+
// than a blank flash before the bubbles stream in.
|
|
1109
|
+
const messagesInitialLoading = isMessagesLoading && !hasMessages
|
|
992
1110
|
// Keys the content region so each distinct view (open conversation, Mingo
|
|
993
1111
|
// welcome, Guide onboarding) remounts on switch → fades in for 200ms, the
|
|
994
1112
|
// same feel as the surface flip. Switching dialogs stays "conversation" so
|
|
@@ -999,7 +1117,7 @@ function EmbeddableChatInner({
|
|
|
999
1117
|
const isGuideEmpty = !hasConversation && activeMode === 'guide'
|
|
1000
1118
|
// The guide-empty back-chevron returns to Mingo — only offer it when Mingo
|
|
1001
1119
|
// mode actually exists to return to (guide is normally entered from Mingo).
|
|
1002
|
-
const guideCanReturnToMingo = isGuideEmpty &&
|
|
1120
|
+
const guideCanReturnToMingo = isGuideEmpty && hasMingoMode
|
|
1003
1121
|
const sourceLabel = source === 'flamingo' ? 'Knowledge Base' : 'Data Room'
|
|
1004
1122
|
|
|
1005
1123
|
// Empty-state chip grid — derived directly from the fetched slash commands.
|
|
@@ -1127,12 +1245,12 @@ function EmbeddableChatInner({
|
|
|
1127
1245
|
: undefined
|
|
1128
1246
|
}
|
|
1129
1247
|
onRename={
|
|
1130
|
-
activeDialogId && activeDialog &&
|
|
1248
|
+
activeDialogId && activeDialog && mingoCaps.canRename
|
|
1131
1249
|
? () => setRenameTarget(activeDialog)
|
|
1132
1250
|
: undefined
|
|
1133
1251
|
}
|
|
1134
1252
|
onArchive={
|
|
1135
|
-
activeDialogId && activeDialog &&
|
|
1253
|
+
activeDialogId && activeDialog && mingoCaps.canArchive
|
|
1136
1254
|
? () => setArchiveTarget(activeDialog)
|
|
1137
1255
|
: undefined
|
|
1138
1256
|
}
|
|
@@ -1144,7 +1262,7 @@ function EmbeddableChatInner({
|
|
|
1144
1262
|
Guide mode is active AND the default (Mingo) mode also exists:
|
|
1145
1263
|
the banner contrasts the temporary Guide session against the
|
|
1146
1264
|
default chat, so it's meaningless in a guide-only setup. */}
|
|
1147
|
-
{activeMode === 'guide' &&
|
|
1265
|
+
{activeMode === 'guide' && hasMingoMode && (
|
|
1148
1266
|
<GuideModeBanner className="animate-in fade-in-0 duration-200" />
|
|
1149
1267
|
)}
|
|
1150
1268
|
|
|
@@ -1164,17 +1282,33 @@ function EmbeddableChatInner({
|
|
|
1164
1282
|
>
|
|
1165
1283
|
{hasConversation ? (
|
|
1166
1284
|
<div className="flex-1 flex flex-col min-h-0">
|
|
1285
|
+
{messagesInitialLoading ? (
|
|
1286
|
+
// Match the real list's `fullWidth` layout so swapping
|
|
1287
|
+
// skeleton → bubbles doesn't shift the column. The panel
|
|
1288
|
+
// wrapper above already pads (`p-[var(--spacing-system-m)]`),
|
|
1289
|
+
// so the skeleton sits flush (no inner px/pb).
|
|
1290
|
+
<ChatMessageListSkeleton
|
|
1291
|
+
fullWidth
|
|
1292
|
+
className="flex-1"
|
|
1293
|
+
/>
|
|
1294
|
+
) : (
|
|
1167
1295
|
<ChatMessageList
|
|
1168
1296
|
messages={messages}
|
|
1169
1297
|
isTyping={chatLoading}
|
|
1170
1298
|
autoScroll={true}
|
|
1171
1299
|
assistantType="mingo"
|
|
1172
|
-
assistantIcon={
|
|
1300
|
+
assistantIcon={mingoAssistantIcon}
|
|
1173
1301
|
renderEntityCard={renderEntityCard}
|
|
1174
1302
|
NavLinkAnchor={NavLinkAnchorViaRuntime}
|
|
1175
1303
|
className="flex-1"
|
|
1304
|
+
// No inner `px`/`pb`: the panel wrapper already pads with
|
|
1305
|
+
// `p-[var(--spacing-system-m)]`. The default content class
|
|
1306
|
+
// adds `px-[var(--spacing-system-m)]` + `pb-…xs`, which
|
|
1307
|
+
// double-inset the thread; `pb-0` overrides via twMerge.
|
|
1308
|
+
contentClassName="max-w-none pb-0"
|
|
1176
1309
|
fullWidth
|
|
1177
1310
|
/>
|
|
1311
|
+
)}
|
|
1178
1312
|
{lastSources &&
|
|
1179
1313
|
(lastSources.cited.length > 0 ||
|
|
1180
1314
|
lastSources.uncited.length > 0) &&
|
|
@@ -1207,20 +1341,19 @@ function EmbeddableChatInner({
|
|
|
1207
1341
|
// Derived internally (returning-user variation) — placed
|
|
1208
1342
|
// after the spread so it can't be overridden by the prop.
|
|
1209
1343
|
hasExistingChats={dialogs.length > 0}
|
|
1344
|
+
isLoadingHistory={dialogsInitialLoading}
|
|
1345
|
+
loadError={dialogsLoadError}
|
|
1346
|
+
onRetry={reloadDialogs}
|
|
1210
1347
|
dialogHistory={
|
|
1211
1348
|
dialogs.length > 0 ? (
|
|
1212
1349
|
<MingoChatHistory
|
|
1213
1350
|
dialogs={dialogs}
|
|
1214
1351
|
onSelectDialog={handleSelectDialog}
|
|
1215
1352
|
onRequestRename={
|
|
1216
|
-
|
|
1217
|
-
? setRenameTarget
|
|
1218
|
-
: undefined
|
|
1353
|
+
mingoCaps.canRename ? setRenameTarget : undefined
|
|
1219
1354
|
}
|
|
1220
1355
|
onRequestArchive={
|
|
1221
|
-
|
|
1222
|
-
? setArchiveTarget
|
|
1223
|
-
: undefined
|
|
1356
|
+
mingoCaps.canArchive ? setArchiveTarget : undefined
|
|
1224
1357
|
}
|
|
1225
1358
|
hasMore={hasMoreDialogs}
|
|
1226
1359
|
isLoadingMore={isDialogsLoading && dialogs.length > 0}
|