@flamingo-stack/openframe-frontend-core 0.0.438 → 0.0.439
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-LR6A67KZ.js → chunk-2ELBNZJ3.js} +2 -2
- package/dist/{chunk-6R5SZPAQ.cjs → chunk-3WJQL4DN.cjs} +15 -15
- package/dist/{chunk-6R5SZPAQ.cjs.map → chunk-3WJQL4DN.cjs.map} +1 -1
- package/dist/{chunk-KBFQF4AV.js → chunk-7WM77CSW.js} +2 -2
- package/dist/{chunk-B5SGPIJ4.cjs → chunk-AUJXLVXA.cjs} +37 -37
- package/dist/{chunk-B5SGPIJ4.cjs.map → chunk-AUJXLVXA.cjs.map} +1 -1
- package/dist/{chunk-X66KRL5G.cjs → chunk-C3FH53AP.cjs} +11 -11
- package/dist/{chunk-X66KRL5G.cjs.map → chunk-C3FH53AP.cjs.map} +1 -1
- package/dist/{chunk-W2PRXXUD.js → chunk-COMBYYQY.js} +2 -2
- package/dist/{chunk-RTHOCS3S.js → chunk-EGRZHR7J.js} +2 -2
- package/dist/{chunk-D5RTZ2WN.js → chunk-F7YVI54P.js} +4 -4
- package/dist/{chunk-5D5SJOLE.js → chunk-G5FALXNO.js} +4 -4
- package/dist/{chunk-2DCP2TIF.cjs → chunk-HTVVXVJL.cjs} +1966 -1490
- package/dist/chunk-HTVVXVJL.cjs.map +1 -0
- package/dist/{chunk-WF7VGEYR.cjs → chunk-IAN7GUIV.cjs} +26 -26
- package/dist/{chunk-WF7VGEYR.cjs.map → chunk-IAN7GUIV.cjs.map} +1 -1
- package/dist/{chunk-SYTL2VIU.cjs → chunk-M3LD6DNE.cjs} +7 -7
- package/dist/{chunk-SYTL2VIU.cjs.map → chunk-M3LD6DNE.cjs.map} +1 -1
- package/dist/{chunk-YKP2F2NC.cjs → chunk-M6QGB6DC.cjs} +26 -26
- package/dist/{chunk-YKP2F2NC.cjs.map → chunk-M6QGB6DC.cjs.map} +1 -1
- package/dist/{chunk-6ETVJ5ON.js → chunk-NQQ4NLUT.js} +6577 -6101
- package/dist/chunk-NQQ4NLUT.js.map +1 -0
- package/dist/{chunk-A25RPWYF.js → chunk-P7PISHM5.js} +2 -2
- package/dist/{chunk-44HNH57W.js → chunk-PBTY7AH5.js} +4 -4
- package/dist/{chunk-ZEY7A3MP.js → chunk-PEIR7D25.js} +2 -2
- package/dist/{chunk-DTYPYYKD.cjs → chunk-R46QEE3R.cjs} +59 -59
- package/dist/{chunk-DTYPYYKD.cjs.map → chunk-R46QEE3R.cjs.map} +1 -1
- package/dist/{chunk-H2TQ44VD.cjs → chunk-RQQZF6G6.cjs} +31 -31
- package/dist/{chunk-H2TQ44VD.cjs.map → chunk-RQQZF6G6.cjs.map} +1 -1
- package/dist/{chunk-CMP56RUC.cjs → chunk-VWGF6IFR.cjs} +62 -62
- package/dist/{chunk-CMP56RUC.cjs.map → chunk-VWGF6IFR.cjs.map} +1 -1
- package/dist/{chunk-POFDZH2C.js → chunk-WAGTZVQN.js} +4 -4
- package/dist/{chunk-XFSDFO6U.js → chunk-WPVBTVI3.js} +2 -2
- package/dist/{chunk-VYG2CJFJ.cjs → chunk-WZ6TCAYQ.cjs} +9 -9
- package/dist/{chunk-VYG2CJFJ.cjs.map → chunk-WZ6TCAYQ.cjs.map} +1 -1
- package/dist/{chunk-PCMDRDYX.cjs → chunk-ZMUFTGL7.cjs} +5 -5
- package/dist/{chunk-PCMDRDYX.cjs.map → chunk-ZMUFTGL7.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/chat-archive-page.d.ts +28 -8
- package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
- package/dist/components/chat/chat-header-icon-button.d.ts +6 -2
- package/dist/components/chat/chat-header-icon-button.d.ts.map +1 -1
- package/dist/components/chat/chat-header-search-field.d.ts +25 -0
- package/dist/components/chat/chat-header-search-field.d.ts.map +1 -0
- package/dist/components/chat/chat-list-empty-state.d.ts +20 -0
- package/dist/components/chat/chat-list-empty-state.d.ts.map +1 -0
- package/dist/components/chat/chat-panel-header.d.ts +19 -9
- package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts +15 -6
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts +2 -0
- package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +4 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +2 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +5 -3
- package/dist/components/chat/mingo-chat-history.d.ts +5 -10
- package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
- package/dist/components/chat/mingo-history-rail.d.ts +54 -0
- package/dist/components/chat/mingo-history-rail.d.ts.map +1 -0
- package/dist/components/chat/mingo-welcome.d.ts +6 -25
- package/dist/components/chat/mingo-welcome.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +130 -128
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +13 -11
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/index.cjs +4 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +5 -3
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -3
- package/package.json +1 -1
- package/src/components/chat/chat-archive-page.tsx +89 -73
- package/src/components/chat/chat-header-icon-button.tsx +12 -5
- package/src/components/chat/chat-header-search-field.tsx +144 -0
- package/src/components/chat/chat-list-empty-state.tsx +44 -0
- package/src/components/chat/chat-panel-header.tsx +76 -29
- package/src/components/chat/embeddable-chat.tsx +548 -125
- package/src/components/chat/guide-welcome.tsx +4 -4
- package/src/components/chat/hooks/use-chat-dialog-manager.ts +59 -5
- package/src/components/chat/index.ts +2 -0
- package/src/components/chat/mingo-chat-history.tsx +46 -100
- package/src/components/chat/mingo-history-rail.tsx +161 -0
- package/src/components/chat/mingo-welcome.tsx +17 -98
- package/src/stories/MingoWelcome.stories.tsx +2 -11
- package/dist/chunk-2DCP2TIF.cjs.map +0 -1
- package/dist/chunk-6ETVJ5ON.js.map +0 -1
- /package/dist/{chunk-LR6A67KZ.js.map → chunk-2ELBNZJ3.js.map} +0 -0
- /package/dist/{chunk-KBFQF4AV.js.map → chunk-7WM77CSW.js.map} +0 -0
- /package/dist/{chunk-W2PRXXUD.js.map → chunk-COMBYYQY.js.map} +0 -0
- /package/dist/{chunk-RTHOCS3S.js.map → chunk-EGRZHR7J.js.map} +0 -0
- /package/dist/{chunk-D5RTZ2WN.js.map → chunk-F7YVI54P.js.map} +0 -0
- /package/dist/{chunk-5D5SJOLE.js.map → chunk-G5FALXNO.js.map} +0 -0
- /package/dist/{chunk-A25RPWYF.js.map → chunk-P7PISHM5.js.map} +0 -0
- /package/dist/{chunk-44HNH57W.js.map → chunk-PBTY7AH5.js.map} +0 -0
- /package/dist/{chunk-ZEY7A3MP.js.map → chunk-PEIR7D25.js.map} +0 -0
- /package/dist/{chunk-POFDZH2C.js.map → chunk-WAGTZVQN.js.map} +0 -0
- /package/dist/{chunk-XFSDFO6U.js.map → chunk-WPVBTVI3.js.map} +0 -0
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
34
|
+
import { cn } from '../../utils/cn'
|
|
34
35
|
import { useControllableState } from '@radix-ui/react-use-controllable-state'
|
|
35
36
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
36
37
|
import { usePreventScroll } from '@react-aria/overlays'
|
|
@@ -47,12 +48,18 @@ import { EntityIcon } from '../icon-display'
|
|
|
47
48
|
import { MingoOnboardingCard } from './mingo-onboarding-card'
|
|
48
49
|
import { MingoOnboardingCardSkeleton } from './mingo-onboarding-card-skeleton'
|
|
49
50
|
import { MingoWelcome, type MingoWelcomeProps } from './mingo-welcome'
|
|
50
|
-
import {
|
|
51
|
+
import { MingoHistoryRail } from './mingo-history-rail'
|
|
51
52
|
import { GuideWelcome, type GuideWelcomeProps } from './guide-welcome'
|
|
52
53
|
import { accentFromIdentityIcon, getAgentAccent } from './quick-action-chip'
|
|
53
54
|
import { GuideModeBanner } from './guide-mode-banner'
|
|
54
55
|
import { PortalContainerContext } from '../ui/portal-container'
|
|
55
|
-
import { ChatPanelHeader } from './chat-panel-header'
|
|
56
|
+
import { ChatPanelHeader, type ChatPanelHeaderProps } from './chat-panel-header'
|
|
57
|
+
import { ChatHeaderIconButton } from './chat-header-icon-button'
|
|
58
|
+
import { ChatHeaderSearchField } from './chat-header-search-field'
|
|
59
|
+
import { ActionsMenuDropdown, type ActionsMenuItem } from '../ui/actions-menu'
|
|
60
|
+
import { Arrow02RightIcon, Arrow02LeftIcon, SearchIcon, ClockHistoryIcon, Ellipsis01Icon, Refresh01LeftIcon } from '../icons-v2-generated'
|
|
61
|
+
import { Chevron02LeftIcon } from '../icons-v2-generated/arrows/chevron-02-left-icon'
|
|
62
|
+
import { XmarkIcon } from '../icons-v2-generated/signs-and-symbols/xmark-icon'
|
|
56
63
|
import { ChatArchivePage } from './chat-archive-page'
|
|
57
64
|
import { ChatComposer } from './chat-composer'
|
|
58
65
|
import { useChatDialogManager } from './hooks/use-chat-dialog-manager'
|
|
@@ -267,18 +274,27 @@ export interface EmbeddableChatProps {
|
|
|
267
274
|
*/
|
|
268
275
|
shell?: 'drawer' | 'none'
|
|
269
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Display name of the signed-in user, shown as the sub-line under the chat
|
|
279
|
+
* title in the panel header. The server-resolved chat identity
|
|
280
|
+
* (`useChatIdentity().user.name`) always wins when present; this is the
|
|
281
|
+
* host-supplied fallback so the header still shows who's signed in when the
|
|
282
|
+
* identity endpoint doesn't return a name (e.g. a tenant whose identity
|
|
283
|
+
* route omits it). Empty/undefined → the header renders the title alone.
|
|
284
|
+
*/
|
|
285
|
+
userDisplayName?: string
|
|
286
|
+
|
|
270
287
|
/**
|
|
271
288
|
* Content overrides for the default (Mingo-mode) empty state
|
|
272
|
-
* (`<MingoWelcome>`): greeting `title`/`subtitle`, the
|
|
273
|
-
*
|
|
289
|
+
* (`<MingoWelcome>`): greeting `title`/`subtitle`, the `promo` card, and
|
|
290
|
+
* extra `quickActions` chips.
|
|
274
291
|
* Each field falls back to the built-in OpenFrame defaults, so the kit
|
|
275
|
-
* stays platform-agnostic. `
|
|
276
|
-
*
|
|
277
|
-
*
|
|
292
|
+
* stays platform-agnostic. `onStartGuideChat` and `hasExistingChats` are
|
|
293
|
+
* wired internally and are NOT overridable here. The quick-action
|
|
294
|
+
* hover-preview callbacks are also wired internally.
|
|
278
295
|
*/
|
|
279
296
|
mingoWelcome?: Omit<
|
|
280
297
|
MingoWelcomeProps,
|
|
281
|
-
| 'userName'
|
|
282
298
|
| 'onStartGuideChat'
|
|
283
299
|
| 'hasExistingChats'
|
|
284
300
|
| 'onQuickActionHover'
|
|
@@ -380,6 +396,99 @@ const mentionTokenOf = (key: string, markerByType: Map<string, string>): string
|
|
|
380
396
|
*/
|
|
381
397
|
const FALLBACK_TOP_RETRIEVED = 3
|
|
382
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Split (wide) Mingo layout metrics (Figma 113:60931 / 113:63630). The
|
|
401
|
+
* "Current Chats" rail is a fixed 320px column; the chat block fills the rest
|
|
402
|
+
* with a 400px floor. Below their sum the panel is too tight for two columns,
|
|
403
|
+
* so the layout falls back to the stacked (single-column) view where the
|
|
404
|
+
* history renders inline in the Mingo empty state. Purely width-driven and
|
|
405
|
+
* self-contained — the host wires nothing.
|
|
406
|
+
*/
|
|
407
|
+
const HISTORY_RAIL_WIDTH = 320
|
|
408
|
+
const CHAT_BLOCK_MIN_WIDTH = 400
|
|
409
|
+
const SPLIT_MIN_WIDTH = HISTORY_RAIL_WIDTH + CHAT_BLOCK_MIN_WIDTH
|
|
410
|
+
|
|
411
|
+
/** Persists the user's rail collapse choice across drawer open/close + reloads. */
|
|
412
|
+
const RAIL_COLLAPSED_STORAGE_KEY = 'mingo-chat-history-collapsed'
|
|
413
|
+
|
|
414
|
+
/** Exit-timer budget (ms) before the collapsed rail unmounts — must cover the
|
|
415
|
+
* longest leave (`RAIL_COLLAPSE_MS`) plus a little slack so the width/opacity
|
|
416
|
+
* transition always settles before the element is removed. */
|
|
417
|
+
const RAIL_ANIM_MS = 320
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Presence + width-transition state for the history rail. The rail animates its
|
|
421
|
+
* WIDTH (320 ⇄ 0) + opacity so the chat block grows/shrinks smoothly alongside
|
|
422
|
+
* it — no hard reflow "jump" when it appears/disappears.
|
|
423
|
+
*
|
|
424
|
+
* - `mounted` — whether to render the rail at all (stays true through the leave
|
|
425
|
+
* so the collapse can play out before unmount).
|
|
426
|
+
* - `active` — drives the expanded (`w-80 opacity-100`) vs collapsed
|
|
427
|
+
* (`w-0 opacity-0`) classes. On a fresh mount it flips true on the next frame
|
|
428
|
+
* so the open transitions from collapsed → expanded (an intro slide).
|
|
429
|
+
*/
|
|
430
|
+
function useRailPresence(open: boolean): { mounted: boolean; active: boolean } {
|
|
431
|
+
const [mounted, setMounted] = useState(open)
|
|
432
|
+
// Start collapsed even when open, so the first paint transitions OPEN.
|
|
433
|
+
const [active, setActive] = useState(false)
|
|
434
|
+
useEffect(() => {
|
|
435
|
+
if (open) {
|
|
436
|
+
setMounted(true)
|
|
437
|
+
// Flip to expanded only AFTER the browser has painted the collapsed
|
|
438
|
+
// (w-0) frame — otherwise the mount + expand coalesce into one paint and
|
|
439
|
+
// the width transition never plays (the rail "pops" open). A single rAF
|
|
440
|
+
// fires before that first paint; a SECOND rAF lands after it, so the
|
|
441
|
+
// enter transition runs from a real w-0 → w-80.
|
|
442
|
+
let raf2 = 0
|
|
443
|
+
const raf1 = requestAnimationFrame(() => {
|
|
444
|
+
raf2 = requestAnimationFrame(() => setActive(true))
|
|
445
|
+
})
|
|
446
|
+
return () => {
|
|
447
|
+
cancelAnimationFrame(raf1)
|
|
448
|
+
cancelAnimationFrame(raf2)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
setActive(false) // collapse (width/opacity transition to 0)
|
|
452
|
+
if (!mounted) return // already gone — nothing to unmount later
|
|
453
|
+
const t = setTimeout(() => setMounted(false), RAIL_ANIM_MS)
|
|
454
|
+
return () => clearTimeout(t)
|
|
455
|
+
}, [open, mounted])
|
|
456
|
+
return { mounted, active }
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Animated container for a history-rail cell (the header's "Current Chats" cell
|
|
461
|
+
* and the body's list column). The OUTER box transitions its width 0 ⇄ 320 +
|
|
462
|
+
* opacity (so the neighbouring chat block slides smoothly); the INNER keeps a
|
|
463
|
+
* fixed `w-80` so the rail content never squishes mid-animation — it's just
|
|
464
|
+
* clipped by the shrinking outer (`overflow-hidden`). One easing both ways;
|
|
465
|
+
* the collapse runs a touch longer than the open for a soft close.
|
|
466
|
+
*/
|
|
467
|
+
function RailSlot({
|
|
468
|
+
active,
|
|
469
|
+
innerClassName,
|
|
470
|
+
children,
|
|
471
|
+
}: {
|
|
472
|
+
active: boolean
|
|
473
|
+
innerClassName?: string
|
|
474
|
+
children: React.ReactNode
|
|
475
|
+
}) {
|
|
476
|
+
return (
|
|
477
|
+
<div
|
|
478
|
+
className={cn(
|
|
479
|
+
// Border on the OUTER so the rail/chat divider tracks the animating
|
|
480
|
+
// boundary (and fades out with the panel) instead of vanishing early.
|
|
481
|
+
'shrink-0 overflow-hidden border-r border-ods-border transition-[width,opacity] ease-out',
|
|
482
|
+
active
|
|
483
|
+
? 'w-80 opacity-100 duration-[240ms]'
|
|
484
|
+
: 'w-0 opacity-0 duration-[300ms]',
|
|
485
|
+
)}
|
|
486
|
+
>
|
|
487
|
+
<div className={cn('h-full w-80', innerClassName)}>{children}</div>
|
|
488
|
+
</div>
|
|
489
|
+
)
|
|
490
|
+
}
|
|
491
|
+
|
|
383
492
|
/**
|
|
384
493
|
* Built-in default route for an OpenFrame AI agent's display config — the
|
|
385
494
|
* lowest-precedence fallback so "agent mode" works with ZERO endpoint wiring.
|
|
@@ -778,6 +887,7 @@ function EmbeddableChatInner({
|
|
|
778
887
|
aiAgentConfigUrl: aiAgentConfigUrlProp,
|
|
779
888
|
defaultActiveMode,
|
|
780
889
|
shell = 'drawer',
|
|
890
|
+
userDisplayName,
|
|
781
891
|
mingoWelcome,
|
|
782
892
|
guideWelcome,
|
|
783
893
|
contextPicker,
|
|
@@ -899,22 +1009,17 @@ function EmbeddableChatInner({
|
|
|
899
1009
|
[commandsUrl],
|
|
900
1010
|
)
|
|
901
1011
|
|
|
902
|
-
//
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
// branch treats `''` the same as missing — embedders that send
|
|
914
|
-
// `firstName: ''` shouldn't render `Hey , I'm Mingo`.
|
|
915
|
-
const userName =
|
|
916
|
-
(identityUser?.firstName?.trim() ||
|
|
917
|
-
identityUser?.name?.split(' ')[0]?.trim()) ||
|
|
1012
|
+
// Header sub-line: the full display name shown under the chat title. The
|
|
1013
|
+
// server-resolved identity wins; the host-supplied `userDisplayName` is the
|
|
1014
|
+
// fallback so the header still names the signed-in user when the identity
|
|
1015
|
+
// endpoint returns no name. Empty → the header shows the title alone.
|
|
1016
|
+
const headerUserName =
|
|
1017
|
+
identityUser?.name?.trim() ||
|
|
1018
|
+
[identityUser?.firstName, identityUser?.lastName]
|
|
1019
|
+
.filter(Boolean)
|
|
1020
|
+
.join(' ')
|
|
1021
|
+
.trim() ||
|
|
1022
|
+
userDisplayName?.trim() ||
|
|
918
1023
|
undefined
|
|
919
1024
|
|
|
920
1025
|
// Synthesize `modes` from legacy props when the new API isn't used.
|
|
@@ -1484,6 +1589,7 @@ function EmbeddableChatInner({
|
|
|
1484
1589
|
archivedDialogs,
|
|
1485
1590
|
archivedCursor,
|
|
1486
1591
|
archivedLoading,
|
|
1592
|
+
archivedPending,
|
|
1487
1593
|
openArchive,
|
|
1488
1594
|
closeArchive,
|
|
1489
1595
|
loadArchivedPage,
|
|
@@ -1492,6 +1598,7 @@ function EmbeddableChatInner({
|
|
|
1492
1598
|
isOpeningDialog,
|
|
1493
1599
|
isViewingArchived,
|
|
1494
1600
|
handleBack,
|
|
1601
|
+
resetToNewChat,
|
|
1495
1602
|
activeDialog,
|
|
1496
1603
|
renameTarget,
|
|
1497
1604
|
setRenameTarget,
|
|
@@ -1650,6 +1757,189 @@ function EmbeddableChatInner({
|
|
|
1650
1757
|
// Host node for in-panel Radix portals (see the body wrapper below).
|
|
1651
1758
|
const [portalHost, setPortalHost] = useState<HTMLDivElement | null>(null)
|
|
1652
1759
|
|
|
1760
|
+
// ── Split (wide) Mingo layout ─────────────────────────────────────────────
|
|
1761
|
+
// The panel measures its own width and, in Mingo mode, promotes the inline
|
|
1762
|
+
// dialog history to a fixed 320px "Current Chats" rail once there's room for
|
|
1763
|
+
// both it and a 400px chat block. Narrower than that (or collapsed, or Guide
|
|
1764
|
+
// mode / archive page) it stays the stacked single-column layout with the
|
|
1765
|
+
// history inline in the empty state. Fully self-contained — the host opts
|
|
1766
|
+
// into nothing; the switch is width-driven inside this component.
|
|
1767
|
+
const [panelWidth, setPanelWidth] = useState(0)
|
|
1768
|
+
const panelMeasureRef = useRef<HTMLDivElement | null>(null)
|
|
1769
|
+
useEffect(() => {
|
|
1770
|
+
const el = panelMeasureRef.current
|
|
1771
|
+
if (!el || typeof ResizeObserver === 'undefined') return
|
|
1772
|
+
const ro = new ResizeObserver((entries) => {
|
|
1773
|
+
const w = entries[0]?.contentRect?.width
|
|
1774
|
+
if (typeof w === 'number') setPanelWidth(w)
|
|
1775
|
+
})
|
|
1776
|
+
ro.observe(el)
|
|
1777
|
+
setPanelWidth(el.clientWidth)
|
|
1778
|
+
return () => ro.disconnect()
|
|
1779
|
+
}, [])
|
|
1780
|
+
|
|
1781
|
+
// Rail collapse toggle (Figma ⟶| control), persisted so it survives the
|
|
1782
|
+
// drawer remounting on close.
|
|
1783
|
+
const [railCollapsed, setRailCollapsed] = useState(false)
|
|
1784
|
+
useEffect(() => {
|
|
1785
|
+
try {
|
|
1786
|
+
if (window.localStorage.getItem(RAIL_COLLAPSED_STORAGE_KEY) === '1')
|
|
1787
|
+
setRailCollapsed(true)
|
|
1788
|
+
} catch {
|
|
1789
|
+
// Storage can throw (private mode) — default to expanded.
|
|
1790
|
+
}
|
|
1791
|
+
}, [])
|
|
1792
|
+
const toggleRailCollapsed = useCallback(() => {
|
|
1793
|
+
setRailCollapsed((prev) => {
|
|
1794
|
+
const next = !prev
|
|
1795
|
+
try {
|
|
1796
|
+
window.localStorage.setItem(RAIL_COLLAPSED_STORAGE_KEY, next ? '1' : '0')
|
|
1797
|
+
} catch {
|
|
1798
|
+
// Best-effort persistence; the in-memory state still applies this session.
|
|
1799
|
+
}
|
|
1800
|
+
return next
|
|
1801
|
+
})
|
|
1802
|
+
}, [])
|
|
1803
|
+
|
|
1804
|
+
// Header magnifier toggle — reveals the rail's built-in list search bar on
|
|
1805
|
+
// demand (matches the Figma header). Only meaningful when search is wired.
|
|
1806
|
+
const [railSearchOpen, setRailSearchOpen] = useState(false)
|
|
1807
|
+
|
|
1808
|
+
// Layout decision. `wideMingo` gates the desktop two-cell header (which hosts
|
|
1809
|
+
// the expand control even while collapsed); `splitActive` additionally
|
|
1810
|
+
// requires the rail to be expanded before its column renders. The archive
|
|
1811
|
+
// page stays split-eligible: in wide mode it opens INSIDE the left rail (the
|
|
1812
|
+
// right chat block stays put), and only falls back to the full-panel archive
|
|
1813
|
+
// when stacked/collapsed.
|
|
1814
|
+
const splitEligible = activeMode === 'mingo'
|
|
1815
|
+
const canSplit = panelWidth >= SPLIT_MIN_WIDTH
|
|
1816
|
+
// Embedded previews (hero demo tabs) always use the compact single-column
|
|
1817
|
+
// header, never the two-column split — so `previewMode` opts out of `wideMingo`.
|
|
1818
|
+
const wideMingo = splitEligible && canSplit && !previewMode
|
|
1819
|
+
const splitActive = wideMingo && !railCollapsed
|
|
1820
|
+
// Keep the "Current Chats" rail (header cell + list column) mounted through
|
|
1821
|
+
// its width/opacity collapse so it eases away instead of hard-cutting; both
|
|
1822
|
+
// synchronized parts wrap in `<RailSlot active={railPresence.active}>`.
|
|
1823
|
+
const railPresence = useRailPresence(splitActive)
|
|
1824
|
+
// The wide header is used for BOTH wide layouts — split (rail beside the chat)
|
|
1825
|
+
// and collapsed (`wideCollapsed`: chat / new-chat welcome in the fill column,
|
|
1826
|
+
// rail hidden). Its built-in corner toggle expands/collapses the rail — the
|
|
1827
|
+
// only chat-list navigation in wide mode, so there's no back chevron here.
|
|
1828
|
+
// `|| railPresence.mounted` keeps it through the rail's exit animation when a
|
|
1829
|
+
// resize drops below the split threshold (wideMingo → narrow).
|
|
1830
|
+
const showWideHeader = wideMingo || railPresence.mounted
|
|
1831
|
+
|
|
1832
|
+
// ── Narrow (stacked) list ⇄ compose navigation ────────────────────────────
|
|
1833
|
+
// The redesigned narrow layout (Figma 341:36190 / 113:69276) splits the old
|
|
1834
|
+
// combined empty state into two views: a "Current Chats" LIST (no composer)
|
|
1835
|
+
// and a "New Chat" COMPOSE view (greeting + composer) reached from the list's
|
|
1836
|
+
// "Start New Chat" button. Wide mode shows both columns at once, so this only
|
|
1837
|
+
// drives the single-column presentation.
|
|
1838
|
+
const [composeOpen, setComposeOpen] = useState(false)
|
|
1839
|
+
// Opening a conversation supersedes the compose view — reset it so backing out
|
|
1840
|
+
// of the conversation lands on the list, not a stale compose screen.
|
|
1841
|
+
useEffect(() => {
|
|
1842
|
+
if (hasConversation) setComposeOpen(false)
|
|
1843
|
+
}, [hasConversation])
|
|
1844
|
+
const isMingoMode = activeMode === 'mingo'
|
|
1845
|
+
// The stacked "Current Chats" list — the NARROW single-column Mingo view. It
|
|
1846
|
+
// is a narrow-only layout: in any wide layout the chat list lives in the rail,
|
|
1847
|
+
// and collapsing the rail (`wideCollapsed`) shows the chat / new-chat welcome
|
|
1848
|
+
// in the fill column (navigation is via re-expanding the rail), NOT the list.
|
|
1849
|
+
// Requires no open conversation, no archive, and not composing (the composer
|
|
1850
|
+
// lives on the compose view).
|
|
1851
|
+
const stackedListView =
|
|
1852
|
+
isMingoMode &&
|
|
1853
|
+
!hasConversation &&
|
|
1854
|
+
!archiveOpen &&
|
|
1855
|
+
!wideMingo &&
|
|
1856
|
+
!composeOpen
|
|
1857
|
+
|
|
1858
|
+
// Shared header derivations — consumed by the stacked `ChatPanelHeader`, its
|
|
1859
|
+
// mobile fallback, and the desktop split header's right cell, so the title /
|
|
1860
|
+
// back / ⋯ semantics can't drift between layouts.
|
|
1861
|
+
const headerShowBack = hasConversation || guideCanReturnToMingo
|
|
1862
|
+
const headerTitle = hasConversation
|
|
1863
|
+
? activeDialog?.title || 'New Chat'
|
|
1864
|
+
: isGuideEmpty
|
|
1865
|
+
? (headerAssistantName ?? 'Mingo Guide')
|
|
1866
|
+
: 'Current Chats'
|
|
1867
|
+
const headerBackAriaLabel = hasConversation
|
|
1868
|
+
? isViewingArchived
|
|
1869
|
+
? 'Back to archive'
|
|
1870
|
+
: 'Back'
|
|
1871
|
+
: 'Back to Mingo'
|
|
1872
|
+
const headerOnBack = hasConversation
|
|
1873
|
+
? handleBack
|
|
1874
|
+
: () => handleActiveModeChange('mingo')
|
|
1875
|
+
const headerOnRestore =
|
|
1876
|
+
isViewingArchived && unarchiveDialog && activeDialog
|
|
1877
|
+
? () => setRestoreTarget(activeDialog)
|
|
1878
|
+
: undefined
|
|
1879
|
+
const headerOnRename =
|
|
1880
|
+
activeDialogId && activeDialog && mingoCaps.canRename
|
|
1881
|
+
? () => setRenameTarget(activeDialog)
|
|
1882
|
+
: undefined
|
|
1883
|
+
const headerOnArchive =
|
|
1884
|
+
activeDialogId && activeDialog && mingoCaps.canArchive
|
|
1885
|
+
? () => setArchiveTarget(activeDialog)
|
|
1886
|
+
: undefined
|
|
1887
|
+
const headerOnOpenArchive = fetchArchivedDialogs ? openArchive : undefined
|
|
1888
|
+
|
|
1889
|
+
// "Start New Chat" (rail button) — reset to the new-chat welcome. Uses the
|
|
1890
|
+
// dedicated reset (not `handleBack`, which would reopen the archive when the
|
|
1891
|
+
// open conversation is archived) so it always lands on a fresh chat.
|
|
1892
|
+
const handleNewChat = useCallback(() => {
|
|
1893
|
+
resetToNewChat()
|
|
1894
|
+
}, [resetToNewChat])
|
|
1895
|
+
|
|
1896
|
+
// Desktop split header ⋯ menu (active, non-archived conversation only).
|
|
1897
|
+
const splitHeaderMenuItems = [
|
|
1898
|
+
headerOnRename && { id: 'rename', label: 'Rename chat', onClick: headerOnRename },
|
|
1899
|
+
headerOnArchive && { id: 'archive', label: 'Archive chat', onClick: headerOnArchive },
|
|
1900
|
+
].filter(Boolean) as ActionsMenuItem[]
|
|
1901
|
+
|
|
1902
|
+
// Narrow (single-column) header. The Mingo empty state splits into a "Current
|
|
1903
|
+
// Chats" list header (search + archive, no back) and a "New Chat" compose
|
|
1904
|
+
// header (back to the list); conversations + guide keep the shared header
|
|
1905
|
+
// derivations. Wide mode uses its own two-cell header instead.
|
|
1906
|
+
const narrowMingoEmpty = isMingoMode && !hasConversation && !archiveOpen
|
|
1907
|
+
const narrowHeaderProps: ChatPanelHeaderProps = narrowMingoEmpty
|
|
1908
|
+
? composeOpen
|
|
1909
|
+
? {
|
|
1910
|
+
showBack: true,
|
|
1911
|
+
title: 'New Chat',
|
|
1912
|
+
subtitle: headerUserName,
|
|
1913
|
+
backAriaLabel: 'Back to chats',
|
|
1914
|
+
onBack: () => setComposeOpen(false),
|
|
1915
|
+
onClose: handleClose,
|
|
1916
|
+
}
|
|
1917
|
+
: {
|
|
1918
|
+
showBack: false,
|
|
1919
|
+
title: 'Current Chats',
|
|
1920
|
+
onClose: handleClose,
|
|
1921
|
+
onOpenArchive: headerOnOpenArchive,
|
|
1922
|
+
onToggleSearch: mingoCaps.onSearchChange
|
|
1923
|
+
? () => setRailSearchOpen((o) => !o)
|
|
1924
|
+
: undefined,
|
|
1925
|
+
searchActive: railSearchOpen,
|
|
1926
|
+
searchQuery: mingoCaps.searchQuery,
|
|
1927
|
+
onSearchChange: mingoCaps.onSearchChange,
|
|
1928
|
+
}
|
|
1929
|
+
: {
|
|
1930
|
+
showBack: headerShowBack,
|
|
1931
|
+
title: headerTitle,
|
|
1932
|
+
subtitle: headerUserName,
|
|
1933
|
+
backAriaLabel: headerBackAriaLabel,
|
|
1934
|
+
isArchivedView: isViewingArchived,
|
|
1935
|
+
onBack: headerOnBack,
|
|
1936
|
+
onClose: handleClose,
|
|
1937
|
+
onRestore: headerOnRestore,
|
|
1938
|
+
onRename: headerOnRename,
|
|
1939
|
+
onArchive: headerOnArchive,
|
|
1940
|
+
onOpenArchive: headerOnOpenArchive,
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1653
1943
|
// Chat body — defined once, then rendered inside whichever shell applies.
|
|
1654
1944
|
// Radix overlays (⋯ menus, tooltips) portal into `portalHost` — a node inside
|
|
1655
1945
|
// this panel — so they inherit the drawer's stacking context and need only a
|
|
@@ -1658,22 +1948,31 @@ function EmbeddableChatInner({
|
|
|
1658
1948
|
const body = (
|
|
1659
1949
|
<PortalContainerContext.Provider value={portalHost}>
|
|
1660
1950
|
{/* Panel surface depends on state (Figma):
|
|
1661
|
-
•
|
|
1662
|
-
`ods-card` (#212121)
|
|
1663
|
-
|
|
1951
|
+
• Narrow "Current Chats" LIST + FULL-PANEL archive page → grey
|
|
1952
|
+
`ods-card` (#212121) — matching the grey rail those lists live
|
|
1953
|
+
in when the panel is wide,
|
|
1954
|
+
• Mingo welcome / compose (node 113:69208), Guide empty
|
|
1955
|
+
(node 7532:328223), and the active conversation → dark
|
|
1664
1956
|
`ods-bg` (#161616).
|
|
1665
1957
|
The header keeps its own grey `bg-ods-card`; the content and
|
|
1666
|
-
footer have no bg and inherit this root, so they flip together.
|
|
1958
|
+
footer have no bg and inherit this root, so they flip together.
|
|
1959
|
+
The archive only tints the root when it's FULL-PANEL (`!splitActive`)
|
|
1960
|
+
— in the split layout it lives in the rail (which owns its own
|
|
1961
|
+
grey), so the chat block on the right must keep the conversation's
|
|
1962
|
+
dark surface instead of following the archive. */}
|
|
1667
1963
|
<div
|
|
1964
|
+
ref={panelMeasureRef}
|
|
1668
1965
|
className={`flex h-full flex-col overflow-hidden transition-colors duration-200 ${
|
|
1669
|
-
archiveOpen
|
|
1966
|
+
(archiveOpen && !splitActive) || stackedListView
|
|
1670
1967
|
? 'bg-ods-card'
|
|
1671
1968
|
: 'bg-ods-bg'
|
|
1672
1969
|
} ${previewMode ? 'pointer-events-none select-none' : ''}`}
|
|
1673
1970
|
>
|
|
1674
1971
|
{/* Archive-page ↔ chat-panel swap fades in (200ms) to match the
|
|
1675
|
-
surface flip
|
|
1676
|
-
|
|
1972
|
+
surface flip. The full-panel archive is used only when NOT in
|
|
1973
|
+
the wide split layout — there the archive opens inside the left
|
|
1974
|
+
rail instead (see the rail column below). */}
|
|
1975
|
+
{archiveOpen && !splitActive ? (
|
|
1677
1976
|
<div
|
|
1678
1977
|
key="archive-view"
|
|
1679
1978
|
className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
|
|
@@ -1684,6 +1983,7 @@ function EmbeddableChatInner({
|
|
|
1684
1983
|
onBack={closeArchive}
|
|
1685
1984
|
onClose={handleClose}
|
|
1686
1985
|
isLoading={archivedLoading}
|
|
1986
|
+
isFetching={archivedPending}
|
|
1687
1987
|
hasMore={archivedCursor != null}
|
|
1688
1988
|
onLoadMore={() => {
|
|
1689
1989
|
void loadArchivedPage(archivedCursor ?? undefined)
|
|
@@ -1695,54 +1995,143 @@ function EmbeddableChatInner({
|
|
|
1695
1995
|
key="chat-view"
|
|
1696
1996
|
className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
|
|
1697
1997
|
>
|
|
1698
|
-
|
|
1699
|
-
//
|
|
1700
|
-
//
|
|
1701
|
-
//
|
|
1702
|
-
|
|
1703
|
-
//
|
|
1704
|
-
//
|
|
1705
|
-
//
|
|
1706
|
-
//
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1998
|
+
{showWideHeader ? (
|
|
1999
|
+
// Wide Mingo header (Figma 113:60931 / 113:63630). Split: a
|
|
2000
|
+
// two-cell bar — the "Current Chats" rail header (search /
|
|
2001
|
+
// archive / collapse) over the 320px rail + the chat-block header
|
|
2002
|
+
// (title / ⋯ / close) over the fill column. Collapsed
|
|
2003
|
+
// (`wideCollapsed`): the rail cell is gone, leaving the corner
|
|
2004
|
+
// expand toggle + the chat-block header (the fill column shows
|
|
2005
|
+
// the chat / new-chat welcome). No back chevron — chat-list
|
|
2006
|
+
// navigation is the rail toggle. Shown whenever the panel is wide
|
|
2007
|
+
// enough to split (`panelWidth ≥ SPLIT_MIN_WIDTH`), NOT gated on
|
|
2008
|
+
// the viewport `md` breakpoint — otherwise a 720–799px panel on a
|
|
2009
|
+
// sub-`md` viewport would split the body yet drop to the mobile
|
|
2010
|
+
// single-bar header (no rail search / collapse controls).
|
|
2011
|
+
<div className="flex h-14 w-full flex-shrink-0 overflow-hidden border-b border-ods-border bg-ods-card">
|
|
2012
|
+
{railPresence.mounted && (
|
|
2013
|
+
<RailSlot
|
|
2014
|
+
active={railPresence.active}
|
|
2015
|
+
innerClassName="flex overflow-hidden"
|
|
2016
|
+
>
|
|
2017
|
+
{archiveOpen ? (
|
|
2018
|
+
// Archive open in the wide layout — the rail header
|
|
2019
|
+
// turns into a "Chat Archive" back bar (the list itself
|
|
2020
|
+
// renders in the rail below); the chat block is untouched.
|
|
2021
|
+
// Back is a full-height leading cell, matching the other
|
|
2022
|
+
// header cells (not a small inline chevron).
|
|
2023
|
+
<>
|
|
2024
|
+
<ChatHeaderIconButton
|
|
2025
|
+
divider="right"
|
|
2026
|
+
onClick={closeArchive}
|
|
2027
|
+
aria-label="Back to chats"
|
|
2028
|
+
>
|
|
2029
|
+
<Chevron02LeftIcon size={24} />
|
|
2030
|
+
</ChatHeaderIconButton>
|
|
2031
|
+
<div className="flex min-w-0 flex-1 items-center px-[var(--spacing-system-mf)]">
|
|
2032
|
+
<span className="min-w-0 flex-1 truncate text-h3 text-ods-text-primary">
|
|
2033
|
+
Chat Archive
|
|
2034
|
+
</span>
|
|
2035
|
+
</div>
|
|
2036
|
+
</>
|
|
2037
|
+
) : (
|
|
2038
|
+
// "Current Chats" rail header — title fills, then the
|
|
2039
|
+
// search + archive controls. When search is open the
|
|
2040
|
+
// inline field takes over the title area in place
|
|
2041
|
+
// (Figma 116:51217) and the magnifier toggle is hidden.
|
|
2042
|
+
<>
|
|
2043
|
+
{railSearchOpen && mingoCaps.onSearchChange ? (
|
|
2044
|
+
<ChatHeaderSearchField
|
|
2045
|
+
initialValue={mingoCaps.searchQuery}
|
|
2046
|
+
onSearchChange={mingoCaps.onSearchChange}
|
|
2047
|
+
onCollapse={() => setRailSearchOpen(false)}
|
|
2048
|
+
/>
|
|
2049
|
+
) : (
|
|
2050
|
+
<>
|
|
2051
|
+
<div className="flex min-w-0 flex-1 items-center px-[var(--spacing-system-mf)]">
|
|
2052
|
+
<span className="min-w-0 flex-1 truncate text-h3 text-ods-text-primary">
|
|
2053
|
+
Current Chats
|
|
2054
|
+
</span>
|
|
2055
|
+
</div>
|
|
2056
|
+
{mingoCaps.onSearchChange && (
|
|
2057
|
+
<ChatHeaderIconButton
|
|
2058
|
+
onClick={() => setRailSearchOpen((o) => !o)}
|
|
2059
|
+
aria-label="Search chats"
|
|
2060
|
+
aria-pressed={railSearchOpen}
|
|
2061
|
+
>
|
|
2062
|
+
<SearchIcon size={24} />
|
|
2063
|
+
</ChatHeaderIconButton>
|
|
2064
|
+
)}
|
|
2065
|
+
</>
|
|
2066
|
+
)}
|
|
2067
|
+
{headerOnOpenArchive && (
|
|
2068
|
+
<ChatHeaderIconButton onClick={headerOnOpenArchive} aria-label="Chat archive">
|
|
2069
|
+
<ClockHistoryIcon size={24} />
|
|
2070
|
+
</ChatHeaderIconButton>
|
|
2071
|
+
)}
|
|
2072
|
+
</>
|
|
2073
|
+
)}
|
|
2074
|
+
</RailSlot>
|
|
2075
|
+
)}
|
|
2076
|
+
|
|
2077
|
+
<div className="flex flex-1 min-w-0">
|
|
2078
|
+
{/* Rail collapse / expand toggle — lives on the chat side,
|
|
2079
|
+
at its left edge (Figma 259:90465). Right arrow collapses
|
|
2080
|
+
the list; left arrow brings it back. */}
|
|
2081
|
+
<button
|
|
2082
|
+
type="button"
|
|
2083
|
+
onClick={toggleRailCollapsed}
|
|
2084
|
+
aria-label={splitActive ? 'Collapse chat list' : 'Show chat list'}
|
|
2085
|
+
aria-pressed={!splitActive}
|
|
2086
|
+
className="flex size-14 shrink-0 items-center justify-center border-r border-ods-border 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"
|
|
2087
|
+
>
|
|
2088
|
+
{splitActive ? <Arrow02RightIcon size={24} /> : <Arrow02LeftIcon size={24} />}
|
|
2089
|
+
</button>
|
|
2090
|
+
{/* No back cell in the wide layout — the left rail (and
|
|
2091
|
+
its collapse/expand toggle) already provides chat-list
|
|
2092
|
+
navigation, so a back chevron would be redundant. */}
|
|
2093
|
+
<div className="flex flex-1 min-w-0 items-center px-[var(--spacing-system-mf)] py-[var(--spacing-system-sf)]">
|
|
2094
|
+
<div className="flex min-w-0 flex-col">
|
|
2095
|
+
<p className="truncate text-h3 leading-tight text-ods-text-primary">
|
|
2096
|
+
{headerShowBack ? headerTitle : 'New Chat'}
|
|
2097
|
+
</p>
|
|
2098
|
+
{headerUserName && (
|
|
2099
|
+
<p className="truncate text-h6 leading-tight text-ods-text-secondary">
|
|
2100
|
+
{headerUserName}
|
|
2101
|
+
</p>
|
|
2102
|
+
)}
|
|
2103
|
+
</div>
|
|
2104
|
+
</div>
|
|
2105
|
+
|
|
2106
|
+
{isViewingArchived && headerOnRestore && (
|
|
2107
|
+
<ChatHeaderIconButton onClick={headerOnRestore} aria-label="Unarchive chat">
|
|
2108
|
+
<Refresh01LeftIcon size={24} />
|
|
2109
|
+
</ChatHeaderIconButton>
|
|
2110
|
+
)}
|
|
2111
|
+
{headerShowBack && !isViewingArchived && splitHeaderMenuItems.length > 0 && (
|
|
2112
|
+
<ActionsMenuDropdown
|
|
2113
|
+
triggerAriaLabel="Chat actions"
|
|
2114
|
+
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
2115
|
+
groups={[{ items: splitHeaderMenuItems }]}
|
|
2116
|
+
customTrigger={
|
|
2117
|
+
<ChatHeaderIconButton aria-label="Chat actions">
|
|
2118
|
+
<Ellipsis01Icon size={24} />
|
|
2119
|
+
</ChatHeaderIconButton>
|
|
2120
|
+
}
|
|
2121
|
+
/>
|
|
2122
|
+
)}
|
|
2123
|
+
<ChatHeaderIconButton onClick={handleClose} aria-label="Close">
|
|
2124
|
+
<XmarkIcon size={24} />
|
|
2125
|
+
</ChatHeaderIconButton>
|
|
2126
|
+
</div>
|
|
2127
|
+
</div>
|
|
2128
|
+
) : (
|
|
2129
|
+
// Narrow single-column header — list / compose / conversation /
|
|
2130
|
+
// guide, resolved into `narrowHeaderProps` above. (Wide layouts,
|
|
2131
|
+
// including rail-collapsed, use the wide header above.) Embedded
|
|
2132
|
+
// previews (hero demo tabs) force the compact bar via `compact`.
|
|
2133
|
+
<ChatPanelHeader {...narrowHeaderProps} compact={previewMode} />
|
|
2134
|
+
)}
|
|
1746
2135
|
|
|
1747
2136
|
{/* Guide-mode indicator banner (Figma node 7532:328222) —
|
|
1748
2137
|
full-bleed accent strip below the header. Shown only when
|
|
@@ -1761,11 +2150,77 @@ function EmbeddableChatInner({
|
|
|
1761
2150
|
places it, mirroring `GuideModeBanner` above. */}
|
|
1762
2151
|
{activeMode === 'mingo' && mingoContextBanner}
|
|
1763
2152
|
|
|
1764
|
-
{/* Chat-panel row.
|
|
1765
|
-
|
|
1766
|
-
|
|
2153
|
+
{/* Chat-panel row. In the wide Mingo layout (`splitActive`) the
|
|
2154
|
+
dialog history is hoisted into a fixed 320px "Current Chats"
|
|
2155
|
+
rail on the left; the stacked layout keeps it inline in the
|
|
2156
|
+
Mingo empty state (`<MingoChatHistory>` via `<MingoWelcome>`). */}
|
|
1767
2157
|
<div className="flex flex-1 min-h-0 overflow-hidden">
|
|
2158
|
+
{railPresence.mounted && (
|
|
2159
|
+
<RailSlot
|
|
2160
|
+
active={railPresence.active}
|
|
2161
|
+
innerClassName="flex flex-col min-h-0 bg-ods-card"
|
|
2162
|
+
>
|
|
2163
|
+
{archiveOpen ? (
|
|
2164
|
+
// Archive list inside the rail (header lives in the split
|
|
2165
|
+
// header's left cell above). Right chat block stays put.
|
|
2166
|
+
<ChatArchivePage
|
|
2167
|
+
embedded
|
|
2168
|
+
dialogs={archivedDialogs}
|
|
2169
|
+
onSelectDialog={handleArchivedSelect}
|
|
2170
|
+
isLoading={archivedLoading}
|
|
2171
|
+
isFetching={archivedPending}
|
|
2172
|
+
hasMore={archivedCursor != null}
|
|
2173
|
+
onLoadMore={() => {
|
|
2174
|
+
void loadArchivedPage(archivedCursor ?? undefined)
|
|
2175
|
+
}}
|
|
2176
|
+
/>
|
|
2177
|
+
) : (
|
|
2178
|
+
<MingoHistoryRail
|
|
2179
|
+
dialogs={dialogs}
|
|
2180
|
+
activeDialogId={activeDialogId ?? undefined}
|
|
2181
|
+
onSelectDialog={handleSelectDialog}
|
|
2182
|
+
onNewChat={handleNewChat}
|
|
2183
|
+
onRequestRename={mingoCaps.canRename ? setRenameTarget : undefined}
|
|
2184
|
+
onRequestArchive={mingoCaps.canArchive ? setArchiveTarget : undefined}
|
|
2185
|
+
searchQuery={mingoCaps.searchQuery}
|
|
2186
|
+
hasMore={hasMoreDialogs}
|
|
2187
|
+
isLoadingMore={isDialogsLoading && dialogs.length > 0}
|
|
2188
|
+
onLoadMore={() => {
|
|
2189
|
+
void loadMoreDialogs()
|
|
2190
|
+
}}
|
|
2191
|
+
isLoadingHistory={dialogsInitialLoading}
|
|
2192
|
+
loadError={dialogsLoadError}
|
|
2193
|
+
onRetry={reloadDialogs}
|
|
2194
|
+
/>
|
|
2195
|
+
)}
|
|
2196
|
+
</RailSlot>
|
|
2197
|
+
)}
|
|
1768
2198
|
<div className="flex flex-1 flex-col min-h-0 min-w-0">
|
|
2199
|
+
{stackedListView ? (
|
|
2200
|
+
// Narrow "Current Chats" list (Figma 341:36190) — the default
|
|
2201
|
+
// single-column view. No composer here; "Start New Chat" opens
|
|
2202
|
+
// the compose view.
|
|
2203
|
+
<MingoHistoryRail
|
|
2204
|
+
className="animate-in fade-in-0 duration-200"
|
|
2205
|
+
dialogs={dialogs}
|
|
2206
|
+
activeDialogId={activeDialogId ?? undefined}
|
|
2207
|
+
onSelectDialog={handleSelectDialog}
|
|
2208
|
+
onNewChat={() => setComposeOpen(true)}
|
|
2209
|
+
newChatAlways
|
|
2210
|
+
onRequestRename={mingoCaps.canRename ? setRenameTarget : undefined}
|
|
2211
|
+
onRequestArchive={mingoCaps.canArchive ? setArchiveTarget : undefined}
|
|
2212
|
+
searchQuery={mingoCaps.searchQuery}
|
|
2213
|
+
hasMore={hasMoreDialogs}
|
|
2214
|
+
isLoadingMore={isDialogsLoading && dialogs.length > 0}
|
|
2215
|
+
onLoadMore={() => {
|
|
2216
|
+
void loadMoreDialogs()
|
|
2217
|
+
}}
|
|
2218
|
+
isLoadingHistory={dialogsInitialLoading}
|
|
2219
|
+
loadError={dialogsLoadError}
|
|
2220
|
+
onRetry={reloadDialogs}
|
|
2221
|
+
/>
|
|
2222
|
+
) : (
|
|
2223
|
+
<>
|
|
1769
2224
|
|
|
1770
2225
|
<div
|
|
1771
2226
|
ref={galleryPanelRef}
|
|
@@ -1843,59 +2298,25 @@ function EmbeddableChatInner({
|
|
|
1843
2298
|
quick-action chips. Guide mode keeps the slash-command
|
|
1844
2299
|
onboarding list below. */
|
|
1845
2300
|
<MingoWelcome
|
|
1846
|
-
userName={userName}
|
|
1847
2301
|
onStartGuideChat={
|
|
1848
2302
|
effectiveModes.guide
|
|
1849
2303
|
? () => handleActiveModeChange('guide')
|
|
1850
2304
|
: undefined
|
|
1851
2305
|
}
|
|
1852
2306
|
{...mingoWelcome}
|
|
1853
|
-
//
|
|
1854
|
-
//
|
|
1855
|
-
// the
|
|
1856
|
-
//
|
|
1857
|
-
|
|
1858
|
-
//
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
onRetry={reloadDialogs}
|
|
1863
|
-
historySearchable={!!mingoCaps.onSearchChange}
|
|
1864
|
-
// Hover/focus PREVIEWS the action's full prompt as ghost
|
|
1865
|
-
// text in the empty composer — same behaviour as Guide-mode
|
|
1866
|
-
// chips (see `quickActionPreview` / `ChatInput.previewText`).
|
|
1867
|
-
// Wired after the `{...mingoWelcome}` spread so the host
|
|
1868
|
-
// can't override it.
|
|
2307
|
+
// History now lives in its own list view (the wide rail or
|
|
2308
|
+
// the narrow "Current Chats" list), so the welcome is always
|
|
2309
|
+
// the new-user compose surface: greeting + grid + promo +
|
|
2310
|
+
// primary Guide chip.
|
|
2311
|
+
hasExistingChats={false}
|
|
2312
|
+
// Hover/focus PREVIEWS the action's full prompt as ghost text
|
|
2313
|
+
// in the empty composer (see `quickActionPreview` /
|
|
2314
|
+
// `ChatInput.previewText`). Wired after the `{...mingoWelcome}`
|
|
2315
|
+
// spread so the host can't override it.
|
|
1869
2316
|
onQuickActionHover={(action) =>
|
|
1870
2317
|
setQuickActionPreview(action.prompt ?? action.label)
|
|
1871
2318
|
}
|
|
1872
2319
|
onQuickActionHoverEnd={() => setQuickActionPreview(null)}
|
|
1873
|
-
dialogHistory={
|
|
1874
|
-
// Keep the history (and its search bar) mounted during an
|
|
1875
|
-
// active search even at 0 results — otherwise a no-match
|
|
1876
|
-
// query would unmount the bar and flash the new-user
|
|
1877
|
-
// greeting. `MingoWelcome` renders `dialogHistory` with top
|
|
1878
|
-
// priority, so this wins over the greeting.
|
|
1879
|
-
dialogs.length > 0 || !!mingoCaps.searchQuery ? (
|
|
1880
|
-
<MingoChatHistory
|
|
1881
|
-
dialogs={dialogs}
|
|
1882
|
-
onSelectDialog={handleSelectDialog}
|
|
1883
|
-
onRequestRename={
|
|
1884
|
-
mingoCaps.canRename ? setRenameTarget : undefined
|
|
1885
|
-
}
|
|
1886
|
-
onRequestArchive={
|
|
1887
|
-
mingoCaps.canArchive ? setArchiveTarget : undefined
|
|
1888
|
-
}
|
|
1889
|
-
searchQuery={mingoCaps.searchQuery}
|
|
1890
|
-
onSearchChange={mingoCaps.onSearchChange}
|
|
1891
|
-
hasMore={hasMoreDialogs}
|
|
1892
|
-
isLoadingMore={isDialogsLoading && dialogs.length > 0}
|
|
1893
|
-
onLoadMore={() => {
|
|
1894
|
-
void loadMoreDialogs()
|
|
1895
|
-
}}
|
|
1896
|
-
/>
|
|
1897
|
-
) : undefined
|
|
1898
|
-
}
|
|
1899
2320
|
/>
|
|
1900
2321
|
) : (
|
|
1901
2322
|
/* Figma node 7532:328214 — Guide-mode empty state: greeting
|
|
@@ -2039,6 +2460,8 @@ function EmbeddableChatInner({
|
|
|
2039
2460
|
breakdown: currentUsageBreakdown ?? undefined,
|
|
2040
2461
|
}}
|
|
2041
2462
|
/>
|
|
2463
|
+
</>
|
|
2464
|
+
)}
|
|
2042
2465
|
</div>
|
|
2043
2466
|
</div>
|
|
2044
2467
|
</div>
|