@flamingo-stack/openframe-frontend-core 0.0.219 → 0.0.220-snapshot.20260602171504

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dist/{chunk-EDW2NVRV.js → chunk-4WZOFD46.js} +37 -37
  2. package/dist/{chunk-EDW2NVRV.js.map → chunk-4WZOFD46.js.map} +1 -1
  3. package/dist/{chunk-ZGBXHK26.cjs → chunk-5GN7TXHY.cjs} +12 -12
  4. package/dist/{chunk-ZGBXHK26.cjs.map → chunk-5GN7TXHY.cjs.map} +1 -1
  5. package/dist/{chunk-F3FO2ZZZ.cjs → chunk-BAKZF4GU.cjs} +7 -7
  6. package/dist/{chunk-F3FO2ZZZ.cjs.map → chunk-BAKZF4GU.cjs.map} +1 -1
  7. package/dist/{chunk-MPHDM2VZ.cjs → chunk-BCL24DFU.cjs} +30 -30
  8. package/dist/{chunk-MPHDM2VZ.cjs.map → chunk-BCL24DFU.cjs.map} +1 -1
  9. package/dist/{chunk-65CPJ4SX.cjs → chunk-C6ASEPZL.cjs} +30 -30
  10. package/dist/{chunk-65CPJ4SX.cjs.map → chunk-C6ASEPZL.cjs.map} +1 -1
  11. package/dist/{chunk-SZXKKEUH.cjs → chunk-E6B4B7GM.cjs} +46 -30
  12. package/dist/chunk-E6B4B7GM.cjs.map +1 -0
  13. package/dist/{chunk-SRA2QYK6.js → chunk-HUA4XG4S.js} +4 -4
  14. package/dist/{chunk-A3PL6ZCF.js → chunk-KXF3WCPH.js} +6397 -5128
  15. package/dist/chunk-KXF3WCPH.js.map +1 -0
  16. package/dist/{chunk-SL3RGBPX.cjs → chunk-QNYH3WUU.cjs} +9 -9
  17. package/dist/{chunk-SL3RGBPX.cjs.map → chunk-QNYH3WUU.cjs.map} +1 -1
  18. package/dist/{chunk-24Q2WLIU.js → chunk-QYRV6MKX.js} +2 -2
  19. package/dist/{chunk-XG7DFRJL.js → chunk-RCECOGMI.js} +3 -3
  20. package/dist/{chunk-7UZLRI7W.cjs → chunk-SEECETJY.cjs} +3301 -2032
  21. package/dist/chunk-SEECETJY.cjs.map +1 -0
  22. package/dist/{chunk-ZII7TNVA.js → chunk-T5MEXJD5.js} +3 -3
  23. package/dist/{chunk-YX3YQNC4.cjs → chunk-W23DRJAA.cjs} +13 -13
  24. package/dist/{chunk-YX3YQNC4.cjs.map → chunk-W23DRJAA.cjs.map} +1 -1
  25. package/dist/{chunk-DRPECAXO.js → chunk-WR32ZE63.js} +2 -2
  26. package/dist/{chunk-Y3MXGCOW.js → chunk-YZDUOUMB.js} +46 -30
  27. package/dist/chunk-YZDUOUMB.js.map +1 -0
  28. package/dist/components/chat/chat-archive-page.d.ts +25 -0
  29. package/dist/components/chat/chat-archive-page.d.ts.map +1 -0
  30. package/dist/components/chat/chat-composer.d.ts +29 -0
  31. package/dist/components/chat/chat-composer.d.ts.map +1 -0
  32. package/dist/components/chat/chat-header-icon-button.d.ts +14 -0
  33. package/dist/components/chat/chat-header-icon-button.d.ts.map +1 -0
  34. package/dist/components/chat/chat-panel-header.d.ts +32 -0
  35. package/dist/components/chat/chat-panel-header.d.ts.map +1 -0
  36. package/dist/components/chat/embeddable-chat.d.ts +18 -0
  37. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  38. package/dist/components/chat/guide-mode-banner.d.ts +16 -0
  39. package/dist/components/chat/guide-mode-banner.d.ts.map +1 -0
  40. package/dist/components/chat/guide-welcome.d.ts +40 -0
  41. package/dist/components/chat/guide-welcome.d.ts.map +1 -0
  42. package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts +58 -0
  43. package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts.map +1 -0
  44. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +26 -1
  45. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  46. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  47. package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
  48. package/dist/components/chat/index.cjs +29 -5
  49. package/dist/components/chat/index.cjs.map +1 -1
  50. package/dist/components/chat/index.d.ts +9 -0
  51. package/dist/components/chat/index.d.ts.map +1 -1
  52. package/dist/components/chat/index.js +28 -4
  53. package/dist/components/chat/mingo-chat-history.d.ts +37 -0
  54. package/dist/components/chat/mingo-chat-history.d.ts.map +1 -0
  55. package/dist/components/chat/mingo-chat-modals.d.ts +50 -0
  56. package/dist/components/chat/mingo-chat-modals.d.ts.map +1 -0
  57. package/dist/components/chat/mingo-onboarding-card.d.ts.map +1 -1
  58. package/dist/components/chat/mingo-welcome.d.ts +78 -0
  59. package/dist/components/chat/mingo-welcome.d.ts.map +1 -0
  60. package/dist/components/chat/types/unified-chat-state.types.d.ts +6 -0
  61. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  62. package/dist/components/contact/index.cjs +6 -6
  63. package/dist/components/contact/index.js +5 -5
  64. package/dist/components/features/index.cjs +5 -5
  65. package/dist/components/features/index.js +4 -4
  66. package/dist/components/icons-v2-generated/brand-logos/fleet-mdm-logo-grey-icon.d.ts.map +1 -1
  67. package/dist/components/icons-v2-generated/brand-logos/fleet-mdm-logo-icon.d.ts.map +1 -1
  68. package/dist/components/icons-v2-generated/index.cjs +2 -2
  69. package/dist/components/icons-v2-generated/index.js +1 -1
  70. package/dist/components/index.cjs +128 -104
  71. package/dist/components/index.cjs.map +1 -1
  72. package/dist/components/index.js +31 -7
  73. package/dist/components/index.js.map +1 -1
  74. package/dist/components/layout/page-heading.d.ts +7 -6
  75. package/dist/components/layout/page-heading.d.ts.map +1 -1
  76. package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
  77. package/dist/components/navigation/header-mingo-button.d.ts +4 -2
  78. package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
  79. package/dist/components/navigation/index.cjs +5 -5
  80. package/dist/components/navigation/index.js +4 -4
  81. package/dist/components/onboarding-guides/index.cjs +28 -28
  82. package/dist/components/onboarding-guides/index.js +6 -6
  83. package/dist/components/tickets/index.cjs +88 -88
  84. package/dist/components/tickets/index.js +7 -7
  85. package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
  86. package/dist/components/ui/file-manager/index.cjs +50 -50
  87. package/dist/components/ui/file-manager/index.js +1 -1
  88. package/dist/components/ui/index.cjs +29 -5
  89. package/dist/components/ui/index.cjs.map +1 -1
  90. package/dist/components/ui/index.js +28 -4
  91. package/dist/components/ui/modal-v2.d.ts.map +1 -1
  92. package/dist/components/ui/more-actions-menu.d.ts +8 -1
  93. package/dist/components/ui/more-actions-menu.d.ts.map +1 -1
  94. package/dist/components/ui/portal-container.d.ts +21 -0
  95. package/dist/components/ui/portal-container.d.ts.map +1 -0
  96. package/dist/components/ui/tooltip.d.ts.map +1 -1
  97. package/dist/hooks/index.cjs +3 -3
  98. package/dist/hooks/index.js +2 -2
  99. package/dist/index.cjs +29 -5
  100. package/dist/index.cjs.map +1 -1
  101. package/dist/index.js +28 -4
  102. package/package.json +1 -1
  103. package/src/components/chat/chat-archive-page.tsx +93 -0
  104. package/src/components/chat/chat-composer.tsx +99 -0
  105. package/src/components/chat/chat-header-icon-button.tsx +36 -0
  106. package/src/components/chat/chat-panel-header.tsx +114 -0
  107. package/src/components/chat/embeddable-chat.tsx +386 -311
  108. package/src/components/chat/guide-mode-banner.tsx +75 -0
  109. package/src/components/chat/guide-welcome.tsx +207 -0
  110. package/src/components/chat/hooks/use-chat-dialog-manager.ts +227 -0
  111. package/src/components/chat/hooks/use-nats-chat-adapter.ts +85 -0
  112. package/src/components/chat/hooks/use-sse-chat-adapter.ts +8 -0
  113. package/src/components/chat/hooks/use-unified-chat.ts +12 -0
  114. package/src/components/chat/index.ts +9 -0
  115. package/src/components/chat/mingo-chat-history.tsx +308 -0
  116. package/src/components/chat/mingo-chat-modals.tsx +223 -0
  117. package/src/components/chat/mingo-onboarding-card.tsx +5 -8
  118. package/src/components/chat/mingo-welcome.tsx +396 -0
  119. package/src/components/chat/types/unified-chat-state.types.ts +8 -0
  120. package/src/components/icons-v2/brand-logos/fleet-mdm-logo-grey.svg +6 -6
  121. package/src/components/icons-v2/brand-logos/fleet-mdm-logo.svg +6 -6
  122. package/src/components/icons-v2-generated/brand-logos/fleet-mdm-logo-grey-icon.tsx +2 -22
  123. package/src/components/icons-v2-generated/brand-logos/fleet-mdm-logo-icon.tsx +22 -2
  124. package/src/components/layout/page-heading.tsx +13 -7
  125. package/src/components/navigation/app-header.tsx +12 -12
  126. package/src/components/navigation/app-layout-drawer.tsx +25 -15
  127. package/src/components/navigation/header-mingo-button.tsx +22 -7
  128. package/src/components/ui/dropdown-menu.tsx +9 -3
  129. package/src/components/ui/modal-v2.tsx +33 -3
  130. package/src/components/ui/more-actions-menu.tsx +15 -2
  131. package/src/components/ui/portal-container.tsx +28 -0
  132. package/src/components/ui/tooltip.tsx +9 -3
  133. package/src/stories/AppLayoutSidebar.stories.tsx +184 -0
  134. package/src/stories/EmbeddableChat.stories.tsx +114 -0
  135. package/src/stories/GuideWelcome.stories.tsx +102 -0
  136. package/src/stories/MingoChatModals.stories.tsx +82 -0
  137. package/src/stories/MingoWelcome.stories.tsx +119 -0
  138. package/dist/chunk-7UZLRI7W.cjs.map +0 -1
  139. package/dist/chunk-A3PL6ZCF.js.map +0 -1
  140. package/dist/chunk-SZXKKEUH.cjs.map +0 -1
  141. package/dist/chunk-Y3MXGCOW.js.map +0 -1
  142. /package/dist/{chunk-SRA2QYK6.js.map → chunk-HUA4XG4S.js.map} +0 -0
  143. /package/dist/{chunk-24Q2WLIU.js.map → chunk-QYRV6MKX.js.map} +0 -0
  144. /package/dist/{chunk-XG7DFRJL.js.map → chunk-RCECOGMI.js.map} +0 -0
  145. /package/dist/{chunk-ZII7TNVA.js.map → chunk-T5MEXJD5.js.map} +0 -0
  146. /package/dist/{chunk-DRPECAXO.js.map → chunk-WR32ZE63.js.map} +0 -0
@@ -42,19 +42,23 @@ import { Button } from '../ui/button'
42
42
  import { Drawer, DrawerContent } from '../ui/drawer'
43
43
  import { HoverDropdown, type HoverDropdownItem } from '../ui/hover-dropdown'
44
44
  import { MingoIcon } from '../icons'
45
- import { Chevron01LeftIcon } from '../icons-v2-generated/arrows/chevron-01-left-icon'
46
- import { XmarkIcon } from '../icons-v2-generated/signs-and-symbols/xmark-icon'
47
45
 
48
- import { ChatFooter } from './chat-container'
49
- import { ChatInput } from './chat-input'
50
- import { ChatSidebar } from './chat-sidebar'
51
46
  import { MingoOnboardingCard } from './mingo-onboarding-card'
52
47
  import { MingoOnboardingCardSkeleton } from './mingo-onboarding-card-skeleton'
48
+ import { MingoWelcome, type MingoWelcomeProps } from './mingo-welcome'
49
+ import { MingoChatHistory } from './mingo-chat-history'
50
+ import { GuideWelcome, type GuideWelcomeProps } from './guide-welcome'
51
+ import { GuideModeBanner } from './guide-mode-banner'
52
+ import { PortalContainerContext } from '../ui/portal-container'
53
+ import { ChatPanelHeader } from './chat-panel-header'
54
+ import { ChatArchivePage } from './chat-archive-page'
55
+ import { ChatComposer } from './chat-composer'
56
+ import { useChatDialogManager } from './hooks/use-chat-dialog-manager'
57
+ import { ChatDialogModals } from './mingo-chat-modals'
53
58
  import { ChatMessageList } from './chat-message-list'
54
- import { ModelDisplay } from './model-display'
55
59
  import { SourceActionButton } from './source-action-button'
56
60
  import { NavLinkAnchorViaRuntime } from './nav-link-anchor-via-runtime'
57
- import { ChatAttachmentAddButton, ChatAttachmentChipStrip } from './chat-attachment-bar'
61
+ import { ChatAttachmentChipStrip } from './chat-attachment-bar'
58
62
  import { renderChatInlineEntityCard } from './entity-cards/dispatch'
59
63
  import type { ChatCardDispatchExtras } from './entity-cards/dispatch'
60
64
 
@@ -66,7 +70,11 @@ import {
66
70
  type ChatMode,
67
71
  type UseUnifiedChatModes,
68
72
  } from './hooks/use-unified-chat'
69
- import type { UseNatsChatAdapterConfig } from './hooks/use-nats-chat-adapter'
73
+ import type {
74
+ UseNatsChatAdapterConfig,
75
+ FetchDialogsParams,
76
+ FetchDialogsResult,
77
+ } from './hooks/use-nats-chat-adapter'
70
78
  import { useChatAttachments } from './hooks/use-chat-attachments'
71
79
  import { useChatAttachmentImageGallery } from './hooks/use-chat-attachment-image-gallery'
72
80
  import { useChatIdentity } from './hooks/use-chat-identity'
@@ -183,6 +191,27 @@ export interface EmbeddableChatProps {
183
191
  * drives).
184
192
  */
185
193
  shell?: 'drawer' | 'none'
194
+
195
+ /**
196
+ * Content overrides for the default (Mingo-mode) empty state
197
+ * (`<MingoWelcome>`): greeting `title`/`subtitle`, the capability
198
+ * `featureCards` grid, the `promo` card, and extra `quickActions` chips.
199
+ * Each field falls back to the built-in OpenFrame defaults, so the kit
200
+ * stays platform-agnostic. `userName`, `onStartGuideChat` and
201
+ * `hasExistingChats` are wired internally and are NOT overridable here.
202
+ */
203
+ mingoWelcome?: Omit<
204
+ MingoWelcomeProps,
205
+ 'userName' | 'onStartGuideChat' | 'hasExistingChats'
206
+ >
207
+
208
+ /**
209
+ * Content overrides for the Guide-mode empty state (`<GuideWelcome>`):
210
+ * greeting `title`/`subtitle` and the `quickActions` chips. Each field falls
211
+ * back to the built-in OpenFrame defaults. `onQuickAction` and the
212
+ * slash-command list `children` are wired internally and not overridable.
213
+ */
214
+ guideWelcome?: Omit<GuideWelcomeProps, 'onQuickAction' | 'children'>
186
215
  }
187
216
 
188
217
  // =============================================================================
@@ -580,6 +609,8 @@ function EmbeddableChatInner({
580
609
  onActiveModeChange,
581
610
  defaultActiveMode,
582
611
  shell = 'drawer',
612
+ mingoWelcome,
613
+ guideWelcome,
583
614
  }: EmbeddableChatProps) {
584
615
  // `shell === 'none'` means the consumer hosts us inside their own panel
585
616
  // (e.g. AppLayoutDrawer in openframe-frontend). Several drawer-shell
@@ -764,10 +795,6 @@ function EmbeddableChatInner({
764
795
  [controlledActiveMode, onActiveModeChange],
765
796
  )
766
797
 
767
- // Mode toggle visible only when both slots are populated.
768
- const showModeToggle =
769
- effectiveModes.guide !== undefined && effectiveModes.mingo !== undefined
770
-
771
798
  const {
772
799
  messages: rawMessages,
773
800
  isLoading: chatLoading,
@@ -783,46 +810,17 @@ function EmbeddableChatInner({
783
810
  currentCacheHitRatePct,
784
811
  currentUsageBreakdown,
785
812
  displayRef,
786
- // ─── Dialog management (Mingo-mode sidebar) ───
813
+ // ─── Dialog management (Mingo-mode inline history) ───
787
814
  dialogs,
788
815
  activeDialogId,
789
816
  selectDialog,
790
- startNewDialog,
817
+ renameDialog,
818
+ archiveDialog,
791
819
  isDialogsLoading,
792
820
  hasMoreDialogs,
793
821
  loadMoreDialogs,
794
822
  } = useUnifiedChat({ modes: effectiveModes, activeMode })
795
823
 
796
- // Whether the in-panel dialog sidebar should render. Gated on:
797
- // 1. Mingo mode is active (Guide has localStorage-only history that
798
- // isn't yet structured as a sidebar list — see
799
- // [[chat-architecture-and-migration]] for the asymmetry).
800
- // 2. The host wired `fetchDialogs` — managed-dialog mode, not the
801
- // bare-transport Tauri flow.
802
- const showSidebar =
803
- activeMode === 'mingo' && effectiveModes.mingo?.fetchDialogs !== undefined
804
-
805
- // Pending startNewDialog promise — used to gate the "Start new chat"
806
- // button while creation is in flight so a double-click doesn't spawn
807
- // two backend dialogs.
808
- const [isStartingNewDialog, setIsStartingNewDialog] = useState<boolean>(false)
809
- const handleStartNewDialog = useCallback(async () => {
810
- if (isStartingNewDialog) return
811
- setIsStartingNewDialog(true)
812
- try {
813
- await startNewDialog()
814
- } finally {
815
- setIsStartingNewDialog(false)
816
- }
817
- }, [isStartingNewDialog, startNewDialog])
818
-
819
- const handleSelectDialog = useCallback(
820
- (id: string) => {
821
- selectDialog(id)
822
- },
823
- [selectDialog],
824
- )
825
-
826
824
  // Chat-attachment hooks (v2 attachment feature).
827
825
  const {
828
826
  attachments: stagedAttachments,
@@ -918,9 +916,44 @@ function EmbeddableChatInner({
918
916
  [sendMessage, readyAttachments, viewUrlPrefix, clearAttachments],
919
917
  )
920
918
 
921
- const handleNewChat = useCallback(() => {
922
- clearMessages()
923
- }, [clearMessages])
919
+ // Dialog-history concerns (archive page, read-only archived conversation,
920
+ // rename/archive/unarchive modals) live in one hook so this component stays
921
+ // a thin orchestrator. Destructured with the names the JSX below uses.
922
+ const {
923
+ fetchArchivedDialogs,
924
+ unarchiveDialog,
925
+ archiveOpen,
926
+ archivedDialogs,
927
+ archivedCursor,
928
+ archivedLoading,
929
+ openArchive,
930
+ closeArchive,
931
+ loadArchivedPage,
932
+ handleArchivedSelect,
933
+ handleSelectDialog,
934
+ isOpeningDialog,
935
+ isViewingArchived,
936
+ handleBack,
937
+ activeDialog,
938
+ renameTarget,
939
+ setRenameTarget,
940
+ archiveTarget,
941
+ setArchiveTarget,
942
+ restoreTarget,
943
+ setRestoreTarget,
944
+ handleConfirmRename,
945
+ handleConfirmArchive,
946
+ handleConfirmRestore,
947
+ } = useChatDialogManager({
948
+ dialogs,
949
+ activeDialogId,
950
+ selectDialog,
951
+ clearMessages,
952
+ renameDialog,
953
+ archiveDialog,
954
+ fetchArchivedDialogs: effectiveModes.mingo?.fetchArchivedDialogs,
955
+ unarchiveDialog: effectiveModes.mingo?.unarchiveDialog,
956
+ })
924
957
 
925
958
  const handleOpen = useCallback(() => setIsOpen(true), [setIsOpen])
926
959
 
@@ -950,9 +983,24 @@ function EmbeddableChatInner({
950
983
  }, [source, discussRef, setIsOpen])
951
984
 
952
985
  const hasMessages = messages.length > 0
986
+ // A conversation is "open" the instant a chat is selected (`isOpeningDialog` /
987
+ // `isViewingArchived` are set synchronously on click) — not only once its
988
+ // history has loaded (`hasMessages`). Driving the surface + content branch
989
+ // off this makes the normal-chat open animate identically to the archived
990
+ // one instead of lagging behind the message fetch.
991
+ const hasConversation = hasMessages || isOpeningDialog || isViewingArchived
992
+ // Keys the content region so each distinct view (open conversation, Mingo
993
+ // welcome, Guide onboarding) remounts on switch → fades in for 200ms, the
994
+ // same feel as the surface flip. Switching dialogs stays "conversation" so
995
+ // streaming messages don't re-trigger the fade.
996
+ const contentViewKey = hasConversation ? 'conversation' : activeMode
997
+ // Guide-mode empty state (no open conversation) — drives the "Mingo Guide"
998
+ // header, the guide banner, and the GuideWelcome content branch.
999
+ const isGuideEmpty = !hasConversation && activeMode === 'guide'
1000
+ // The guide-empty back-chevron returns to Mingo — only offer it when Mingo
1001
+ // mode actually exists to return to (guide is normally entered from Mingo).
1002
+ const guideCanReturnToMingo = isGuideEmpty && !!effectiveModes.mingo
953
1003
  const sourceLabel = source === 'flamingo' ? 'Knowledge Base' : 'Data Room'
954
- const greetingText =
955
- emptyStateGreeting || `Ask me anything about ${sourceLabel.toLowerCase()}.`
956
1004
 
957
1005
  // Empty-state chip grid — derived directly from the fetched slash commands.
958
1006
  const enabledSet = useMemo(
@@ -998,138 +1046,121 @@ function EmbeddableChatInner({
998
1046
  }, [lastAssistantMsg, chatLoading])
999
1047
 
1000
1048
 
1001
- return (
1002
- <>
1003
- {/* Floating "Ask AI" button — sticky-dock pattern. See hub original
1004
- for the full mechanism explanation. Suppressed in shell-less mode:
1005
- the host controls open/close, so an internal trigger would race. */}
1006
- {showInternalTrigger && !shellLess && (
1007
- <div
1008
- aria-hidden={isOpen}
1009
- className={`sticky bottom-0 h-0 z-[9990] pointer-events-none ${
1010
- isOpen ? 'opacity-0' : 'opacity-100'
1011
- }`}
1012
- >
1013
- <div className="absolute bottom-4 md:bottom-6 right-4 md:right-6">
1014
- <Button
1015
- onClick={handleOpen}
1016
- leftIcon={<MingoIcon className="h-5 w-5" color="currentColor" />}
1017
- tabIndex={isOpen ? -1 : 0}
1018
- className={`shadow-lg !w-auto pointer-events-auto ${
1019
- isOpen ? '!pointer-events-none' : ''
1049
+ // Host node for in-panel Radix portals (see the body wrapper below).
1050
+ const [portalHost, setPortalHost] = useState<HTMLDivElement | null>(null)
1051
+
1052
+ // Chat body defined once, then rendered inside whichever shell applies.
1053
+ // Radix overlays (⋯ menus, tooltips) portal into `portalHost` a node inside
1054
+ // this panel so they inherit the drawer's stacking context and need only a
1055
+ // small, local z-index instead of escalating to beat the drawer at the
1056
+ // document root. See `PortalContainerContext`.
1057
+ const body = (
1058
+ <PortalContainerContext.Provider value={portalHost}>
1059
+ {/* Panel surface depends on state (Figma):
1060
+ • Mingo empty / returning-user + archive page → grey
1061
+ `ods-card` (#212121),
1062
+ • Guide empty (node 7532:328223) + active conversation → dark
1063
+ `ods-bg` (#161616).
1064
+ The header keeps its own grey `bg-ods-card`; the content and
1065
+ footer have no bg and inherit this root, so they flip together. */}
1066
+ <div
1067
+ className={`flex h-full flex-col overflow-hidden transition-colors duration-200 ${
1068
+ archiveOpen || (!hasConversation && activeMode === 'mingo')
1069
+ ? 'bg-ods-card'
1070
+ : 'bg-ods-bg'
1020
1071
  }`}
1021
1072
  >
1022
- Ask AI
1023
- </Button>
1024
- </div>
1025
- </div>
1026
- )}
1027
-
1028
- {/*
1029
- Conditional shell — overlay (Drawer, MPH default) vs inline
1030
- (positioned `<aside>` anchored to the nearest positioned
1031
- ancestor, openframe-frontend's nav-embedded chat). Both share
1032
- the same `body` JSX so the chat content is defined exactly
1033
- once; the IIFE keeps both branches type-balanced inside JSX
1034
- (no opening tag in one ternary branch + closing in another).
1035
- */}
1036
- {(() => {
1037
- const body = (
1038
- <>
1039
- <div className="flex h-full flex-col overflow-hidden">
1040
- {/* Figma node 7363:205930 — top-navigation. Title intentionally
1041
- omitted; a chevron-left + "New Chat" back-style affordance
1042
- appears on the left when a conversation is active
1043
- (hasMessages) and resets the chat. Close on the right.
1044
- Left-borders act as 1px cell dividers. */}
1045
- <div className="flex-shrink-0 flex h-14 w-full overflow-hidden border-b border-ods-border bg-ods-card">
1046
- <div className="flex flex-1 min-w-0 items-center gap-2 px-4 py-3">
1047
- {hasMessages ? (
1048
- <>
1049
- <button
1050
- type="button"
1051
- onClick={handleNewChat}
1052
- aria-label="Start a new chat"
1053
- className="inline-flex shrink-0 items-center justify-center size-8 -ml-1 rounded-md text-ods-text-secondary transition-colors hover:bg-ods-bg-hover hover:text-ods-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
1054
- >
1055
- <Chevron01LeftIcon size={20} />
1056
- </button>
1057
- <span className="truncate text-h3 text-ods-text-primary">
1058
- New Chat
1059
- </span>
1060
- </>
1061
- ) : (
1062
- <p className="truncate text-h4 text-ods-text-secondary">
1063
- Start a conversation
1064
- </p>
1065
- )}
1066
- </div>
1067
- <button
1068
- type="button"
1069
- onClick={handleClose}
1070
- className="flex size-14 shrink-0 items-center justify-center border-l border-ods-border text-ods-text-primary transition-colors hover:bg-ods-bg-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
1071
- aria-label="Close"
1073
+ {/* Archive-page ↔ chat-panel swap fades in (200ms) to match the
1074
+ surface flip — both branches share the same view-change feel. */}
1075
+ {archiveOpen ? (
1076
+ <div
1077
+ key="archive-view"
1078
+ className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
1072
1079
  >
1073
- <XmarkIcon className="text-ods-text-secondary" size={24} />
1074
- </button>
1075
- </div>
1080
+ <ChatArchivePage
1081
+ dialogs={archivedDialogs}
1082
+ onSelectDialog={handleArchivedSelect}
1083
+ onBack={closeArchive}
1084
+ onClose={handleClose}
1085
+ isLoading={archivedLoading}
1086
+ hasMore={archivedCursor != null}
1087
+ onLoadMore={() => {
1088
+ void loadArchivedPage(archivedCursor ?? undefined)
1089
+ }}
1090
+ />
1091
+ </div>
1092
+ ) : (
1093
+ <div
1094
+ key="chat-view"
1095
+ className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
1096
+ >
1097
+ <ChatPanelHeader
1098
+ // Guide-mode empty state shows a back-chevron + "Mingo Guide"
1099
+ // (back returns to the default Mingo welcome); an open
1100
+ // conversation shows back + the dialog title; the Mingo list
1101
+ // keeps the static "Current Chats" title.
1102
+ showBack={hasConversation || guideCanReturnToMingo}
1103
+ title={
1104
+ hasConversation
1105
+ ? activeDialog?.title || 'New Chat'
1106
+ : isGuideEmpty
1107
+ ? 'Mingo Guide'
1108
+ : 'Current Chats'
1109
+ }
1110
+ backAriaLabel={
1111
+ hasConversation
1112
+ ? isViewingArchived
1113
+ ? 'Back to archive'
1114
+ : 'Back'
1115
+ : 'Back to Mingo'
1116
+ }
1117
+ isArchivedView={isViewingArchived}
1118
+ onBack={
1119
+ hasConversation
1120
+ ? handleBack
1121
+ : () => handleActiveModeChange('mingo')
1122
+ }
1123
+ onClose={handleClose}
1124
+ onRestore={
1125
+ isViewingArchived && unarchiveDialog && activeDialog
1126
+ ? () => setRestoreTarget(activeDialog)
1127
+ : undefined
1128
+ }
1129
+ onRename={
1130
+ activeDialogId && activeDialog && effectiveModes.mingo?.renameDialog
1131
+ ? () => setRenameTarget(activeDialog)
1132
+ : undefined
1133
+ }
1134
+ onArchive={
1135
+ activeDialogId && activeDialog && effectiveModes.mingo?.archiveDialog
1136
+ ? () => setArchiveTarget(activeDialog)
1137
+ : undefined
1138
+ }
1139
+ onOpenArchive={fetchArchivedDialogs ? openArchive : undefined}
1140
+ />
1141
+
1142
+ {/* Guide-mode indicator banner (Figma node 7532:328222) —
1143
+ full-bleed accent strip below the header whenever Guide mode
1144
+ is active. Fades in to match the panel's view transitions. */}
1145
+ {activeMode === 'guide' && (
1146
+ <GuideModeBanner className="animate-in fade-in-0 duration-200" />
1147
+ )}
1076
1148
 
1077
- {/* Sidebar + chat-panel row. When Mingo is the active mode and
1078
- `fetchDialogs` is wired, the in-panel `<ChatSidebar>` lists
1079
- the user's backend-driven dialog history. Guide mode keeps
1080
- history in localStorage and currently renders no sidebar. */}
1149
+ {/* Chat-panel row. The dialog history is rendered inline in the
1150
+ Mingo empty state (`<MingoChatHistory>`), so there's no
1151
+ separate left sidebar. */}
1081
1152
  <div className="flex flex-1 min-h-0 overflow-hidden">
1082
- {showSidebar && (
1083
- <ChatSidebar
1084
- className="w-72 shrink-0"
1085
- dialogs={dialogs}
1086
- activeDialogId={activeDialogId ?? undefined}
1087
- onDialogSelect={handleSelectDialog}
1088
- onNewChat={() => { void handleStartNewDialog() }}
1089
- isLoading={isDialogsLoading && dialogs.length === 0}
1090
- isCreatingDialog={isStartingNewDialog}
1091
- hasNextPage={hasMoreDialogs}
1092
- isFetchingNextPage={isDialogsLoading && dialogs.length > 0}
1093
- onLoadMore={() => { void loadMoreDialogs() }}
1094
- />
1095
- )}
1096
1153
  <div className="flex flex-1 flex-col min-h-0 min-w-0">
1097
1154
 
1098
- {showModeToggle ? (
1099
- <div
1100
- role="radiogroup"
1101
- aria-label="Chat mode"
1102
- className="flex-shrink-0 mx-5 mt-3 inline-flex rounded-lg border border-ods-border bg-ods-bg-secondary p-0.5 self-start"
1103
- >
1104
- {(['mingo', 'guide'] as ChatMode[]).map((m) => {
1105
- const isActive = activeMode === m
1106
- const label = m === 'mingo' ? 'Mingo' : 'Guide'
1107
- return (
1108
- <button
1109
- key={m}
1110
- type="button"
1111
- role="radio"
1112
- aria-checked={isActive}
1113
- onClick={() => handleActiveModeChange(m)}
1114
- className={
1115
- 'px-3 py-1 text-sm rounded-md transition-colors ' +
1116
- (isActive
1117
- ? 'bg-ods-accent text-ods-text-on-accent'
1118
- : 'text-ods-text-secondary hover:text-ods-text-primary')
1119
- }
1120
- >
1121
- {label}
1122
- </button>
1123
- )
1124
- })}
1125
- </div>
1126
- ) : null}
1127
-
1128
1155
  <div
1129
1156
  ref={galleryPanelRef}
1130
- className="flex-1 flex flex-col min-h-0 px-5 py-4"
1157
+ className="flex-1 flex flex-col min-h-0 p-[var(--spacing-system-m)]"
1131
1158
  >
1132
- {hasMessages ? (
1159
+ <div
1160
+ key={contentViewKey}
1161
+ className="flex-1 flex flex-col min-h-0 animate-in fade-in-0 duration-200"
1162
+ >
1163
+ {hasConversation ? (
1133
1164
  <div className="flex-1 flex flex-col min-h-0">
1134
1165
  <ChatMessageList
1135
1166
  messages={messages}
@@ -1158,38 +1189,67 @@ function EmbeddableChatInner({
1158
1189
  </div>
1159
1190
  )}
1160
1191
  </div>
1192
+ ) : activeMode === 'mingo' ? (
1193
+ /* Figma node 7532:222444 — default (Mingo-mode) empty state:
1194
+ centred greeting + capability grid + Guide-chat promo +
1195
+ quick-action chips. Guide mode keeps the slash-command
1196
+ onboarding list below. */
1197
+ <MingoWelcome
1198
+ userName={userName}
1199
+ onStartGuideChat={
1200
+ effectiveModes.guide
1201
+ ? () => handleActiveModeChange('guide')
1202
+ : undefined
1203
+ }
1204
+ {...mingoWelcome}
1205
+ // Derived internally (returning-user variation) — placed
1206
+ // after the spread so it can't be overridden by the prop.
1207
+ hasExistingChats={dialogs.length > 0}
1208
+ dialogHistory={
1209
+ dialogs.length > 0 ? (
1210
+ <MingoChatHistory
1211
+ dialogs={dialogs}
1212
+ onSelectDialog={handleSelectDialog}
1213
+ onRequestRename={
1214
+ effectiveModes.mingo?.renameDialog
1215
+ ? setRenameTarget
1216
+ : undefined
1217
+ }
1218
+ onRequestArchive={
1219
+ effectiveModes.mingo?.archiveDialog
1220
+ ? setArchiveTarget
1221
+ : undefined
1222
+ }
1223
+ hasMore={hasMoreDialogs}
1224
+ isLoadingMore={isDialogsLoading && dialogs.length > 0}
1225
+ onLoadMore={() => {
1226
+ void loadMoreDialogs()
1227
+ }}
1228
+ />
1229
+ ) : undefined
1230
+ }
1231
+ />
1161
1232
  ) : (
1162
- <div className="flex-1 flex flex-col min-h-0">
1163
- {/* Figma node 7363:206278 data-placeholder: logo + greeting.
1164
- Pinned at the top (flex-shrink-0); the card list below
1165
- owns its own scroll so the greeting stays visible while
1166
- users browse the commands. */}
1167
- <div className="flex-shrink-0 flex flex-col items-center justify-center gap-6 p-6 w-full text-center">
1168
- <MingoIcon
1169
- className="h-12 w-12"
1170
- color="white"
1171
- eyesColor="var(--ods-flamingo-cyan-base)"
1172
- cornerColor="var(--ods-flamingo-cyan-base)"
1173
- />
1174
- <div className="flex flex-col gap-1 w-full">
1175
- <p className="text-h4 text-ods-text-primary">
1176
- {userName ? `Hey ${userName}, I'm Mingo` : "Hey, I'm Mingo"}
1177
- </p>
1178
- <p className="text-h6 text-ods-text-secondary whitespace-pre-line">
1179
- {greetingText}
1180
- </p>
1181
- </div>
1182
- </div>
1183
-
1184
- {/* Figma node 7363:205938 — single-column slash-command list.
1185
- The list container has no own bg (inherits the darker
1186
- `ods-bg` from the drawer panel); each card has the
1187
- lighter `ods-card` to pop on the dark surface.
1188
- `flex-1 min-h-0 overflow-y-auto` makes the LIST the
1189
- scroll target — the rounded-md frame stays visible
1190
- while inner cards scroll past it. */}
1233
+ /* Figma node 7532:328214 — Guide-mode empty state: greeting
1234
+ + slash-command onboarding list share one scroll region,
1235
+ with a pinned quick-action chip row above the composer. */
1236
+ <GuideWelcome
1237
+ // Legacy `emptyStateGreeting` still customises the guide
1238
+ // subtitle; an explicit `guideWelcome.subtitle` wins.
1239
+ subtitle={emptyStateGreeting ?? undefined}
1240
+ {...guideWelcome}
1241
+ onQuickAction={(action) => {
1242
+ chatInputRef.current?.setValue(
1243
+ action.prompt ?? action.label,
1244
+ )
1245
+ chatInputRef.current?.focus()
1246
+ }}
1247
+ >
1248
+ {/* Figma node 7363:205938 — single-column slash-command
1249
+ list. No own scroll (GuideWelcome's region scrolls); the
1250
+ rounded-md frame holds the cards on the dark surface. */}
1191
1251
  {(chipCommands.length > 0 || !commandsLoaded) && (
1192
- <div className="mx-4 mb-4 flex-1 min-h-0 overflow-y-auto rounded-md border border-ods-border">
1252
+ <div className="shrink-0 overflow-hidden rounded-md border border-ods-border">
1193
1253
  {!commandsLoaded &&
1194
1254
  chipCommands.length === 0 &&
1195
1255
  SKELETON_ROW_VARIANTS.map((variant, i) => (
@@ -1227,8 +1287,9 @@ function EmbeddableChatInner({
1227
1287
  })}
1228
1288
  </div>
1229
1289
  )}
1230
- </div>
1290
+ </GuideWelcome>
1231
1291
  )}
1292
+ </div>
1232
1293
  </div>
1233
1294
 
1234
1295
  <ChatAttachmentChipStrip
@@ -1237,122 +1298,136 @@ function EmbeddableChatInner({
1237
1298
  disabled={chatLoading}
1238
1299
  />
1239
1300
 
1240
- {/* Figma node 7363:205952 — footer area on the lighter
1241
- `ods-card` surface to contrast with the darker body
1242
- (`ods-bg`) above. */}
1243
- <div
1244
- className="flex-shrink-0 px-5 pt-3 pb-4 flex flex-col gap-2 bg-ods-card border-t border-ods-border"
1245
- style={{ paddingBottom: 'max(1rem, env(safe-area-inset-bottom))' }}
1246
- >
1247
- <ChatFooter className="!p-0" fullWidth>
1248
- <ChatInput
1249
- ref={chatInputRef}
1250
- onSend={handleSend}
1251
- onStop={stopMessage}
1252
- sending={chatLoading || hasInflightUploads}
1253
- placeholder={
1254
- hasInflightUploads
1255
- ? 'Waiting for uploads to finish…'
1256
- : 'Ask a question...'
1257
- }
1258
- fullWidth
1259
- className="px-0"
1260
- reserveAvatarOffset={false}
1261
- autoFocus={autoFocusInput}
1262
- slashCommands={slashCommandsProp}
1263
- />
1264
- </ChatFooter>
1265
-
1266
- <div className="flex items-center gap-2 w-full">
1267
- {attachmentsEnabled && activeMode === 'guide' && (
1268
- // Attachments are Guide-only: the NATS agent backend
1269
- // doesn't accept them, so the add-button is hidden in
1270
- // Mingo mode regardless of the identity endpoint's
1271
- // capability flag. Skipping the render entirely (not
1272
- // just the icon) collapses the otherwise-invisible 28px
1273
- // placeholder slot the component leaves for layout.
1274
- <ChatAttachmentAddButton
1275
- attachmentsEnabled
1276
- attachmentsCount={stagedAttachments.length}
1277
- onAddFiles={addAttachmentFiles}
1278
- disabled={chatLoading}
1279
- />
1280
- )}
1281
- <div className="flex-1 min-w-0">
1282
- <ModelDisplay
1283
- provider={currentProvider ?? 'anthropic'}
1284
- modelName={currentModelLabel ?? 'Claude'}
1285
- usedTokens={
1286
- currentInputTokens != null
1287
- ? (currentInputTokens ?? 0) + (currentOutputTokens ?? 0)
1288
- : undefined
1289
- }
1290
- contextWindow={currentContextWindowMaxTokens ?? undefined}
1291
- inputTokens={currentInputTokens ?? undefined}
1292
- outputTokens={currentOutputTokens ?? undefined}
1293
- hitRatePct={currentCacheHitRatePct ?? undefined}
1294
- breakdown={currentUsageBreakdown ?? undefined}
1295
- />
1296
- </div>
1301
+ <ChatComposer
1302
+ archived={isViewingArchived}
1303
+ inputRef={chatInputRef}
1304
+ onSend={handleSend}
1305
+ onStop={stopMessage}
1306
+ sending={chatLoading || hasInflightUploads}
1307
+ placeholder={
1308
+ hasInflightUploads
1309
+ ? 'Waiting for uploads to finish…'
1310
+ : 'Ask a question...'
1311
+ }
1312
+ autoFocus={autoFocusInput}
1313
+ slashCommands={slashCommandsProp}
1314
+ showAttachmentButton={attachmentsEnabled && activeMode === 'guide'}
1315
+ attachmentsCount={stagedAttachments.length}
1316
+ onAddFiles={addAttachmentFiles}
1317
+ attachmentsDisabled={chatLoading}
1318
+ model={{
1319
+ provider: currentProvider ?? 'anthropic',
1320
+ modelName: currentModelLabel ?? 'Claude',
1321
+ usedTokens:
1322
+ currentInputTokens != null
1323
+ ? (currentInputTokens ?? 0) + (currentOutputTokens ?? 0)
1324
+ : undefined,
1325
+ contextWindow: currentContextWindowMaxTokens ?? undefined,
1326
+ inputTokens: currentInputTokens ?? undefined,
1327
+ outputTokens: currentOutputTokens ?? undefined,
1328
+ hitRatePct: currentCacheHitRatePct ?? undefined,
1329
+ breakdown: currentUsageBreakdown ?? undefined,
1330
+ }}
1331
+ />
1297
1332
  </div>
1298
1333
  </div>
1299
- </div>
1300
1334
  </div>
1335
+ )}
1301
1336
  </div>
1302
1337
  {galleryModal}
1303
- </>
1304
- )
1305
1338
 
1306
- // Shell-less branch host (e.g. AppLayoutDrawer) provides the panel
1307
- // chrome, focus trap, animation, and size. We render only the chat
1308
- // body plus ChatPanelContext so descendants can still close after
1309
- // same-tab navigation via the host's `onOpenChange`.
1310
- if (shellLess) {
1311
- return (
1312
- <ChatPanelContext.Provider value={chatPanelHandle}>
1313
- {body}
1314
- </ChatPanelContext.Provider>
1315
- )
1316
- }
1339
+ {/* Rename / Archive / Unarchive chat modals (Figma 7592:225962,
1340
+ 7592:226181). Triggered from the header and the dialog-history
1341
+ rows; rendered inside the panel so they overlay the chat. */}
1342
+ <ChatDialogModals
1343
+ renameTarget={renameTarget}
1344
+ setRenameTarget={setRenameTarget}
1345
+ onConfirmRename={handleConfirmRename}
1346
+ archiveTarget={archiveTarget}
1347
+ setArchiveTarget={setArchiveTarget}
1348
+ onConfirmArchive={handleConfirmArchive}
1349
+ restoreTarget={restoreTarget}
1350
+ setRestoreTarget={setRestoreTarget}
1351
+ onConfirmRestore={handleConfirmRestore}
1352
+ />
1317
1353
 
1318
- // Body-level Radix Drawerbackdrop, iOS scroll-lock, focus
1319
- // trap, drag-to-resize handle. Slides in from the right edge.
1320
- return (
1321
- <Drawer open={isOpen} onOpenChange={(o: boolean) => !o && handleClose()}>
1322
- <ChatPanelContext.Provider value={chatPanelHandle}>
1323
- {/*
1324
- Panel-level handle for descendants (inline cards via
1325
- `ChatCardNavWrap`, markdown-body links via
1326
- `NavLinkAnchorViaRuntime`) to close the panel after same-tab
1327
- navigation. Same-tab clicks fire `closeChat`; new-tab clicks
1328
- leave the panel open while the new tab loads.
1329
- */}
1330
- <DrawerContent
1331
- side="right"
1332
- flush
1333
- resizable
1334
- minSize={showSidebar ? 720 : 480}
1335
- maxSize={1600}
1336
- defaultSize={showSidebar ? 960 : 640}
1337
- storageKey={showSidebar ? 'mingo-chat-width-with-sidebar' : 'mingo-chat-width'}
1338
- resizeAriaLabel="Resize chat panel"
1339
- overlayClassName="mingo-chat-overlay md:bg-black/20"
1340
- aria-describedby={undefined}
1341
- className="
1342
- mingo-chat-content !bg-ods-bg shadow-2xl
1343
- focus:outline-none focus-visible:outline-none
1344
- w-screen md:w-auto
1345
- "
1346
- >
1347
- <VisuallyHidden>
1348
- <DialogPrimitive.Title>{sourceLabel} AI Assistant</DialogPrimitive.Title>
1349
- </VisuallyHidden>
1350
- {body}
1351
- </DrawerContent>
1352
- </ChatPanelContext.Provider>
1353
- </Drawer>
1354
- )
1355
- })()}
1354
+ {/* Portal target for in-panel Radix overlays`display: contents`
1355
+ so it adds no box; content is positioned `fixed` by Radix. */}
1356
+ <div ref={setPortalHost} style={{ display: 'contents' }} />
1357
+ </PortalContainerContext.Provider>
1358
+ )
1359
+
1360
+ // Conditional shell: inline (shell-less host, e.g. AppLayoutDrawer, which
1361
+ // provides its own chrome/focus-trap/size) vs the body-level Radix Drawer
1362
+ // (backdrop, iOS scroll-lock, drag-to-resize). Both render the same `body`.
1363
+ const panel = shellLess ? (
1364
+ <ChatPanelContext.Provider value={chatPanelHandle}>
1365
+ {/* Shell-less hosts (AppLayoutDrawer) own the Radix Dialog but not its
1366
+ accessible name — Radix warns when a Dialog.Content has no Title. The
1367
+ panel supplies its own visually-hidden title here, mirroring the
1368
+ drawer-shell branch below, so it's accessible regardless of host. */}
1369
+ <VisuallyHidden>
1370
+ <DialogPrimitive.Title>{sourceLabel} AI Assistant</DialogPrimitive.Title>
1371
+ </VisuallyHidden>
1372
+ {body}
1373
+ </ChatPanelContext.Provider>
1374
+ ) : (
1375
+ <Drawer open={isOpen} onOpenChange={(o: boolean) => !o && handleClose()}>
1376
+ <ChatPanelContext.Provider value={chatPanelHandle}>
1377
+ {/* Panel-level handle for descendants (inline cards, markdown-body
1378
+ links) to close the panel after same-tab navigation. */}
1379
+ <DrawerContent
1380
+ side="right"
1381
+ flush
1382
+ resizable
1383
+ minSize={480}
1384
+ maxSize={1600}
1385
+ defaultSize={750}
1386
+ storageKey="mingo-chat-width"
1387
+ resizeAriaLabel="Resize chat panel"
1388
+ overlayClassName="mingo-chat-overlay md:bg-black/20"
1389
+ aria-describedby={undefined}
1390
+ className="
1391
+ mingo-chat-content !bg-ods-bg shadow-2xl
1392
+ focus:outline-none focus-visible:outline-none
1393
+ w-screen md:w-auto
1394
+ "
1395
+ >
1396
+ <VisuallyHidden>
1397
+ <DialogPrimitive.Title>{sourceLabel} AI Assistant</DialogPrimitive.Title>
1398
+ </VisuallyHidden>
1399
+ {body}
1400
+ </DrawerContent>
1401
+ </ChatPanelContext.Provider>
1402
+ </Drawer>
1403
+ )
1404
+
1405
+ return (
1406
+ <>
1407
+ {/* Floating "Ask AI" button — sticky-dock pattern. Suppressed in
1408
+ shell-less mode: the host controls open/close. */}
1409
+ {showInternalTrigger && !shellLess && (
1410
+ <div
1411
+ aria-hidden={isOpen}
1412
+ className={`sticky bottom-0 h-0 z-[9990] pointer-events-none ${
1413
+ isOpen ? 'opacity-0' : 'opacity-100'
1414
+ }`}
1415
+ >
1416
+ <div className="absolute bottom-4 md:bottom-6 right-4 md:right-6">
1417
+ <Button
1418
+ onClick={handleOpen}
1419
+ leftIcon={<MingoIcon className="h-5 w-5" color="currentColor" />}
1420
+ tabIndex={isOpen ? -1 : 0}
1421
+ className={`shadow-lg !w-auto pointer-events-auto ${
1422
+ isOpen ? '!pointer-events-none' : ''
1423
+ }`}
1424
+ >
1425
+ Ask AI
1426
+ </Button>
1427
+ </div>
1428
+ </div>
1429
+ )}
1430
+ {panel}
1356
1431
  </>
1357
1432
  )
1358
1433
  }