@flamingo-stack/openframe-frontend-core 0.0.438-snapshot.20260716203356 → 0.0.438
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-HTVVXVJL.cjs → chunk-2DCP2TIF.cjs} +1490 -1966
- package/dist/chunk-2DCP2TIF.cjs.map +1 -0
- package/dist/{chunk-PBTY7AH5.js → chunk-44HNH57W.js} +4 -4
- package/dist/{chunk-G5FALXNO.js → chunk-5D5SJOLE.js} +4 -4
- package/dist/{chunk-NQQ4NLUT.js → chunk-6ETVJ5ON.js} +6101 -6577
- package/dist/chunk-6ETVJ5ON.js.map +1 -0
- package/dist/{chunk-3WJQL4DN.cjs → chunk-6R5SZPAQ.cjs} +15 -15
- package/dist/{chunk-3WJQL4DN.cjs.map → chunk-6R5SZPAQ.cjs.map} +1 -1
- package/dist/{chunk-P7PISHM5.js → chunk-A25RPWYF.js} +2 -2
- package/dist/{chunk-AUJXLVXA.cjs → chunk-B5SGPIJ4.cjs} +37 -37
- package/dist/{chunk-AUJXLVXA.cjs.map → chunk-B5SGPIJ4.cjs.map} +1 -1
- package/dist/{chunk-VWGF6IFR.cjs → chunk-CMP56RUC.cjs} +62 -62
- package/dist/{chunk-VWGF6IFR.cjs.map → chunk-CMP56RUC.cjs.map} +1 -1
- package/dist/{chunk-F7YVI54P.js → chunk-D5RTZ2WN.js} +4 -4
- package/dist/{chunk-R46QEE3R.cjs → chunk-DTYPYYKD.cjs} +59 -59
- package/dist/{chunk-R46QEE3R.cjs.map → chunk-DTYPYYKD.cjs.map} +1 -1
- package/dist/{chunk-RQQZF6G6.cjs → chunk-H2TQ44VD.cjs} +31 -31
- package/dist/{chunk-RQQZF6G6.cjs.map → chunk-H2TQ44VD.cjs.map} +1 -1
- package/dist/{chunk-7WM77CSW.js → chunk-KBFQF4AV.js} +2 -2
- package/dist/{chunk-2ELBNZJ3.js → chunk-LR6A67KZ.js} +2 -2
- package/dist/{chunk-ZMUFTGL7.cjs → chunk-PCMDRDYX.cjs} +5 -5
- package/dist/{chunk-ZMUFTGL7.cjs.map → chunk-PCMDRDYX.cjs.map} +1 -1
- package/dist/{chunk-WAGTZVQN.js → chunk-POFDZH2C.js} +4 -4
- package/dist/{chunk-EGRZHR7J.js → chunk-RTHOCS3S.js} +2 -2
- package/dist/{chunk-M3LD6DNE.cjs → chunk-SYTL2VIU.cjs} +7 -7
- package/dist/{chunk-M3LD6DNE.cjs.map → chunk-SYTL2VIU.cjs.map} +1 -1
- package/dist/{chunk-WZ6TCAYQ.cjs → chunk-VYG2CJFJ.cjs} +9 -9
- package/dist/{chunk-WZ6TCAYQ.cjs.map → chunk-VYG2CJFJ.cjs.map} +1 -1
- package/dist/{chunk-COMBYYQY.js → chunk-W2PRXXUD.js} +2 -2
- package/dist/{chunk-IAN7GUIV.cjs → chunk-WF7VGEYR.cjs} +26 -26
- package/dist/{chunk-IAN7GUIV.cjs.map → chunk-WF7VGEYR.cjs.map} +1 -1
- package/dist/{chunk-C3FH53AP.cjs → chunk-X66KRL5G.cjs} +11 -11
- package/dist/{chunk-C3FH53AP.cjs.map → chunk-X66KRL5G.cjs.map} +1 -1
- package/dist/{chunk-WPVBTVI3.js → chunk-XFSDFO6U.js} +2 -2
- package/dist/{chunk-M6QGB6DC.cjs → chunk-YKP2F2NC.cjs} +26 -26
- package/dist/{chunk-M6QGB6DC.cjs.map → chunk-YKP2F2NC.cjs.map} +1 -1
- package/dist/{chunk-PEIR7D25.js → chunk-ZEY7A3MP.js} +2 -2
- 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 +8 -28
- package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
- package/dist/components/chat/chat-header-icon-button.d.ts +2 -6
- package/dist/components/chat/chat-header-icon-button.d.ts.map +1 -1
- package/dist/components/chat/chat-panel-header.d.ts +9 -19
- package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts +6 -15
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts +0 -2
- package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -4
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +0 -2
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +3 -5
- package/dist/components/chat/mingo-chat-history.d.ts +10 -5
- package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
- package/dist/components/chat/mingo-welcome.d.ts +25 -6
- 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 +128 -130
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -13
- 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 +2 -4
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +3 -5
- package/dist/index.cjs +2 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -5
- package/package.json +1 -1
- package/src/components/chat/chat-archive-page.tsx +73 -89
- package/src/components/chat/chat-header-icon-button.tsx +5 -12
- package/src/components/chat/chat-panel-header.tsx +29 -76
- package/src/components/chat/embeddable-chat.tsx +125 -548
- package/src/components/chat/guide-welcome.tsx +4 -4
- package/src/components/chat/hooks/use-chat-dialog-manager.ts +5 -59
- package/src/components/chat/index.ts +0 -2
- package/src/components/chat/mingo-chat-history.tsx +100 -46
- package/src/components/chat/mingo-welcome.tsx +98 -17
- package/src/stories/MingoWelcome.stories.tsx +11 -2
- package/dist/chunk-HTVVXVJL.cjs.map +0 -1
- package/dist/chunk-NQQ4NLUT.js.map +0 -1
- package/dist/components/chat/chat-header-search-field.d.ts +0 -25
- package/dist/components/chat/chat-header-search-field.d.ts.map +0 -1
- package/dist/components/chat/chat-list-empty-state.d.ts +0 -20
- package/dist/components/chat/chat-list-empty-state.d.ts.map +0 -1
- package/dist/components/chat/mingo-history-rail.d.ts +0 -54
- package/dist/components/chat/mingo-history-rail.d.ts.map +0 -1
- package/src/components/chat/chat-header-search-field.tsx +0 -144
- package/src/components/chat/chat-list-empty-state.tsx +0 -44
- package/src/components/chat/mingo-history-rail.tsx +0 -161
- /package/dist/{chunk-PBTY7AH5.js.map → chunk-44HNH57W.js.map} +0 -0
- /package/dist/{chunk-G5FALXNO.js.map → chunk-5D5SJOLE.js.map} +0 -0
- /package/dist/{chunk-P7PISHM5.js.map → chunk-A25RPWYF.js.map} +0 -0
- /package/dist/{chunk-F7YVI54P.js.map → chunk-D5RTZ2WN.js.map} +0 -0
- /package/dist/{chunk-7WM77CSW.js.map → chunk-KBFQF4AV.js.map} +0 -0
- /package/dist/{chunk-2ELBNZJ3.js.map → chunk-LR6A67KZ.js.map} +0 -0
- /package/dist/{chunk-WAGTZVQN.js.map → chunk-POFDZH2C.js.map} +0 -0
- /package/dist/{chunk-EGRZHR7J.js.map → chunk-RTHOCS3S.js.map} +0 -0
- /package/dist/{chunk-COMBYYQY.js.map → chunk-W2PRXXUD.js.map} +0 -0
- /package/dist/{chunk-WPVBTVI3.js.map → chunk-XFSDFO6U.js.map} +0 -0
- /package/dist/{chunk-PEIR7D25.js.map → chunk-ZEY7A3MP.js.map} +0 -0
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
34
|
-
import { cn } from '../../utils/cn'
|
|
35
34
|
import { useControllableState } from '@radix-ui/react-use-controllable-state'
|
|
36
35
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
37
36
|
import { usePreventScroll } from '@react-aria/overlays'
|
|
@@ -48,18 +47,12 @@ import { EntityIcon } from '../icon-display'
|
|
|
48
47
|
import { MingoOnboardingCard } from './mingo-onboarding-card'
|
|
49
48
|
import { MingoOnboardingCardSkeleton } from './mingo-onboarding-card-skeleton'
|
|
50
49
|
import { MingoWelcome, type MingoWelcomeProps } from './mingo-welcome'
|
|
51
|
-
import {
|
|
50
|
+
import { MingoChatHistory } from './mingo-chat-history'
|
|
52
51
|
import { GuideWelcome, type GuideWelcomeProps } from './guide-welcome'
|
|
53
52
|
import { accentFromIdentityIcon, getAgentAccent } from './quick-action-chip'
|
|
54
53
|
import { GuideModeBanner } from './guide-mode-banner'
|
|
55
54
|
import { PortalContainerContext } from '../ui/portal-container'
|
|
56
|
-
import { ChatPanelHeader
|
|
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'
|
|
55
|
+
import { ChatPanelHeader } from './chat-panel-header'
|
|
63
56
|
import { ChatArchivePage } from './chat-archive-page'
|
|
64
57
|
import { ChatComposer } from './chat-composer'
|
|
65
58
|
import { useChatDialogManager } from './hooks/use-chat-dialog-manager'
|
|
@@ -274,27 +267,18 @@ export interface EmbeddableChatProps {
|
|
|
274
267
|
*/
|
|
275
268
|
shell?: 'drawer' | 'none'
|
|
276
269
|
|
|
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
|
-
|
|
287
270
|
/**
|
|
288
271
|
* Content overrides for the default (Mingo-mode) empty state
|
|
289
|
-
* (`<MingoWelcome>`): greeting `title`/`subtitle`, the
|
|
290
|
-
* extra `quickActions` chips.
|
|
272
|
+
* (`<MingoWelcome>`): greeting `title`/`subtitle`, the capability
|
|
273
|
+
* `featureCards` grid, the `promo` card, and extra `quickActions` chips.
|
|
291
274
|
* Each field falls back to the built-in OpenFrame defaults, so the kit
|
|
292
|
-
* stays platform-agnostic. `onStartGuideChat` and
|
|
293
|
-
* wired internally and are NOT overridable here.
|
|
294
|
-
* hover-preview callbacks are also wired internally.
|
|
275
|
+
* stays platform-agnostic. `userName`, `onStartGuideChat` and
|
|
276
|
+
* `hasExistingChats` are wired internally and are NOT overridable here.
|
|
277
|
+
* The quick-action hover-preview callbacks are also wired internally.
|
|
295
278
|
*/
|
|
296
279
|
mingoWelcome?: Omit<
|
|
297
280
|
MingoWelcomeProps,
|
|
281
|
+
| 'userName'
|
|
298
282
|
| 'onStartGuideChat'
|
|
299
283
|
| 'hasExistingChats'
|
|
300
284
|
| 'onQuickActionHover'
|
|
@@ -396,99 +380,6 @@ const mentionTokenOf = (key: string, markerByType: Map<string, string>): string
|
|
|
396
380
|
*/
|
|
397
381
|
const FALLBACK_TOP_RETRIEVED = 3
|
|
398
382
|
|
|
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
|
-
|
|
492
383
|
/**
|
|
493
384
|
* Built-in default route for an OpenFrame AI agent's display config — the
|
|
494
385
|
* lowest-precedence fallback so "agent mode" works with ZERO endpoint wiring.
|
|
@@ -887,7 +778,6 @@ function EmbeddableChatInner({
|
|
|
887
778
|
aiAgentConfigUrl: aiAgentConfigUrlProp,
|
|
888
779
|
defaultActiveMode,
|
|
889
780
|
shell = 'drawer',
|
|
890
|
-
userDisplayName,
|
|
891
781
|
mingoWelcome,
|
|
892
782
|
guideWelcome,
|
|
893
783
|
contextPicker,
|
|
@@ -1009,17 +899,22 @@ function EmbeddableChatInner({
|
|
|
1009
899
|
[commandsUrl],
|
|
1010
900
|
)
|
|
1011
901
|
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
1014
|
-
//
|
|
1015
|
-
//
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
.
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
902
|
+
// Greeting first-name comes from the SERVER-resolved identity (single
|
|
903
|
+
// source of truth — never a client-injected runtime field). Resolution
|
|
904
|
+
// order:
|
|
905
|
+
// 1. `identityUser.firstName` — dedicated optional field from the
|
|
906
|
+
// identity webservice (populated via `X-Chat-First-Name` for
|
|
907
|
+
// bearer-act-as, or by the hub's profile lookup for cookie sessions).
|
|
908
|
+
// 2. `identityUser.name.split(' ')[0]` — legacy fallback for sources
|
|
909
|
+
// that only return a full name. Empty-string-safe (`?.`-chain).
|
|
910
|
+
// 3. `undefined` — anon, loading, or no name available → greeting
|
|
911
|
+
// collapses to the no-name variant `Hey, I'm Mingo`.
|
|
912
|
+
// We coalesce an empty string to `undefined` so the JSX `userName ? …`
|
|
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()) ||
|
|
1023
918
|
undefined
|
|
1024
919
|
|
|
1025
920
|
// Synthesize `modes` from legacy props when the new API isn't used.
|
|
@@ -1589,7 +1484,6 @@ function EmbeddableChatInner({
|
|
|
1589
1484
|
archivedDialogs,
|
|
1590
1485
|
archivedCursor,
|
|
1591
1486
|
archivedLoading,
|
|
1592
|
-
archivedPending,
|
|
1593
1487
|
openArchive,
|
|
1594
1488
|
closeArchive,
|
|
1595
1489
|
loadArchivedPage,
|
|
@@ -1598,7 +1492,6 @@ function EmbeddableChatInner({
|
|
|
1598
1492
|
isOpeningDialog,
|
|
1599
1493
|
isViewingArchived,
|
|
1600
1494
|
handleBack,
|
|
1601
|
-
resetToNewChat,
|
|
1602
1495
|
activeDialog,
|
|
1603
1496
|
renameTarget,
|
|
1604
1497
|
setRenameTarget,
|
|
@@ -1757,189 +1650,6 @@ function EmbeddableChatInner({
|
|
|
1757
1650
|
// Host node for in-panel Radix portals (see the body wrapper below).
|
|
1758
1651
|
const [portalHost, setPortalHost] = useState<HTMLDivElement | null>(null)
|
|
1759
1652
|
|
|
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
|
-
|
|
1943
1653
|
// Chat body — defined once, then rendered inside whichever shell applies.
|
|
1944
1654
|
// Radix overlays (⋯ menus, tooltips) portal into `portalHost` — a node inside
|
|
1945
1655
|
// this panel — so they inherit the drawer's stacking context and need only a
|
|
@@ -1948,31 +1658,22 @@ function EmbeddableChatInner({
|
|
|
1948
1658
|
const body = (
|
|
1949
1659
|
<PortalContainerContext.Provider value={portalHost}>
|
|
1950
1660
|
{/* Panel surface depends on state (Figma):
|
|
1951
|
-
•
|
|
1952
|
-
`ods-card` (#212121)
|
|
1953
|
-
|
|
1954
|
-
• Mingo welcome / compose (node 113:69208), Guide empty
|
|
1955
|
-
(node 7532:328223), and the active conversation → dark
|
|
1661
|
+
• Mingo empty / returning-user + archive page → grey
|
|
1662
|
+
`ods-card` (#212121),
|
|
1663
|
+
• Guide empty (node 7532:328223) + active conversation → dark
|
|
1956
1664
|
`ods-bg` (#161616).
|
|
1957
1665
|
The header keeps its own grey `bg-ods-card`; the content and
|
|
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. */}
|
|
1666
|
+
footer have no bg and inherit this root, so they flip together. */}
|
|
1963
1667
|
<div
|
|
1964
|
-
ref={panelMeasureRef}
|
|
1965
1668
|
className={`flex h-full flex-col overflow-hidden transition-colors duration-200 ${
|
|
1966
|
-
|
|
1669
|
+
archiveOpen || (!hasConversation && activeMode === 'mingo')
|
|
1967
1670
|
? 'bg-ods-card'
|
|
1968
1671
|
: 'bg-ods-bg'
|
|
1969
1672
|
} ${previewMode ? 'pointer-events-none select-none' : ''}`}
|
|
1970
1673
|
>
|
|
1971
1674
|
{/* Archive-page ↔ chat-panel swap fades in (200ms) to match the
|
|
1972
|
-
surface flip
|
|
1973
|
-
|
|
1974
|
-
rail instead (see the rail column below). */}
|
|
1975
|
-
{archiveOpen && !splitActive ? (
|
|
1675
|
+
surface flip — both branches share the same view-change feel. */}
|
|
1676
|
+
{archiveOpen ? (
|
|
1976
1677
|
<div
|
|
1977
1678
|
key="archive-view"
|
|
1978
1679
|
className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
|
|
@@ -1983,7 +1684,6 @@ function EmbeddableChatInner({
|
|
|
1983
1684
|
onBack={closeArchive}
|
|
1984
1685
|
onClose={handleClose}
|
|
1985
1686
|
isLoading={archivedLoading}
|
|
1986
|
-
isFetching={archivedPending}
|
|
1987
1687
|
hasMore={archivedCursor != null}
|
|
1988
1688
|
onLoadMore={() => {
|
|
1989
1689
|
void loadArchivedPage(archivedCursor ?? undefined)
|
|
@@ -1995,143 +1695,54 @@ function EmbeddableChatInner({
|
|
|
1995
1695
|
key="chat-view"
|
|
1996
1696
|
className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
|
|
1997
1697
|
>
|
|
1998
|
-
|
|
1999
|
-
//
|
|
2000
|
-
//
|
|
2001
|
-
//
|
|
2002
|
-
|
|
2003
|
-
//
|
|
2004
|
-
//
|
|
2005
|
-
//
|
|
2006
|
-
//
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
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
|
-
)}
|
|
1698
|
+
<ChatPanelHeader
|
|
1699
|
+
// Embedded previews (the hero demo tabs) keep the small bar at
|
|
1700
|
+
// every width — the phone-sized full-screen header (large title)
|
|
1701
|
+
// is wrong in a small embedded panel.
|
|
1702
|
+
compact={previewMode}
|
|
1703
|
+
// Guide-mode empty state shows a back-chevron + "Mingo Guide"
|
|
1704
|
+
// (back returns to the default Mingo welcome); an open
|
|
1705
|
+
// conversation shows back + the dialog title; the Mingo list
|
|
1706
|
+
// keeps the static "Current Chats" title.
|
|
1707
|
+
showBack={hasConversation || guideCanReturnToMingo}
|
|
1708
|
+
title={
|
|
1709
|
+
hasConversation
|
|
1710
|
+
? activeDialog?.title || 'New Chat'
|
|
1711
|
+
: isGuideEmpty
|
|
1712
|
+
? (headerAssistantName ?? 'Mingo Guide')
|
|
1713
|
+
: 'Current Chats'
|
|
1714
|
+
}
|
|
1715
|
+
backAriaLabel={
|
|
1716
|
+
hasConversation
|
|
1717
|
+
? isViewingArchived
|
|
1718
|
+
? 'Back to archive'
|
|
1719
|
+
: 'Back'
|
|
1720
|
+
: 'Back to Mingo'
|
|
1721
|
+
}
|
|
1722
|
+
isArchivedView={isViewingArchived}
|
|
1723
|
+
onBack={
|
|
1724
|
+
hasConversation
|
|
1725
|
+
? handleBack
|
|
1726
|
+
: () => handleActiveModeChange('mingo')
|
|
1727
|
+
}
|
|
1728
|
+
onClose={handleClose}
|
|
1729
|
+
onRestore={
|
|
1730
|
+
isViewingArchived && unarchiveDialog && activeDialog
|
|
1731
|
+
? () => setRestoreTarget(activeDialog)
|
|
1732
|
+
: undefined
|
|
1733
|
+
}
|
|
1734
|
+
onRename={
|
|
1735
|
+
activeDialogId && activeDialog && mingoCaps.canRename
|
|
1736
|
+
? () => setRenameTarget(activeDialog)
|
|
1737
|
+
: undefined
|
|
1738
|
+
}
|
|
1739
|
+
onArchive={
|
|
1740
|
+
activeDialogId && activeDialog && mingoCaps.canArchive
|
|
1741
|
+
? () => setArchiveTarget(activeDialog)
|
|
1742
|
+
: undefined
|
|
1743
|
+
}
|
|
1744
|
+
onOpenArchive={fetchArchivedDialogs ? openArchive : undefined}
|
|
1745
|
+
/>
|
|
2135
1746
|
|
|
2136
1747
|
{/* Guide-mode indicator banner (Figma node 7532:328222) —
|
|
2137
1748
|
full-bleed accent strip below the header. Shown only when
|
|
@@ -2150,77 +1761,11 @@ function EmbeddableChatInner({
|
|
|
2150
1761
|
places it, mirroring `GuideModeBanner` above. */}
|
|
2151
1762
|
{activeMode === 'mingo' && mingoContextBanner}
|
|
2152
1763
|
|
|
2153
|
-
{/* Chat-panel row.
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
Mingo empty state (`<MingoChatHistory>` via `<MingoWelcome>`). */}
|
|
1764
|
+
{/* Chat-panel row. The dialog history is rendered inline in the
|
|
1765
|
+
Mingo empty state (`<MingoChatHistory>`), so there's no
|
|
1766
|
+
separate left sidebar. */}
|
|
2157
1767
|
<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
|
-
)}
|
|
2198
1768
|
<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
|
-
<>
|
|
2224
1769
|
|
|
2225
1770
|
<div
|
|
2226
1771
|
ref={galleryPanelRef}
|
|
@@ -2298,25 +1843,59 @@ function EmbeddableChatInner({
|
|
|
2298
1843
|
quick-action chips. Guide mode keeps the slash-command
|
|
2299
1844
|
onboarding list below. */
|
|
2300
1845
|
<MingoWelcome
|
|
1846
|
+
userName={userName}
|
|
2301
1847
|
onStartGuideChat={
|
|
2302
1848
|
effectiveModes.guide
|
|
2303
1849
|
? () => handleActiveModeChange('guide')
|
|
2304
1850
|
: undefined
|
|
2305
1851
|
}
|
|
2306
1852
|
{...mingoWelcome}
|
|
2307
|
-
//
|
|
2308
|
-
//
|
|
2309
|
-
// the
|
|
2310
|
-
//
|
|
2311
|
-
|
|
2312
|
-
//
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
1853
|
+
// NOTE: admin "try-asking chips" (suggestedQueries) are
|
|
1854
|
+
// intentionally NOT surfaced in Mingo mode — they belong to
|
|
1855
|
+
// the Guide-mode empty state only. Mingo shows just the
|
|
1856
|
+
// host-provided `mingoWelcome.quickActions` (via the spread).
|
|
1857
|
+
// Derived internally (returning-user variation) — placed
|
|
1858
|
+
// after the spread so it can't be overridden by the prop.
|
|
1859
|
+
hasExistingChats={dialogs.length > 0}
|
|
1860
|
+
isLoadingHistory={dialogsInitialLoading}
|
|
1861
|
+
loadError={dialogsLoadError}
|
|
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.
|
|
2316
1869
|
onQuickActionHover={(action) =>
|
|
2317
1870
|
setQuickActionPreview(action.prompt ?? action.label)
|
|
2318
1871
|
}
|
|
2319
1872
|
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
|
+
}
|
|
2320
1899
|
/>
|
|
2321
1900
|
) : (
|
|
2322
1901
|
/* Figma node 7532:328214 — Guide-mode empty state: greeting
|
|
@@ -2460,8 +2039,6 @@ function EmbeddableChatInner({
|
|
|
2460
2039
|
breakdown: currentUsageBreakdown ?? undefined,
|
|
2461
2040
|
}}
|
|
2462
2041
|
/>
|
|
2463
|
-
</>
|
|
2464
|
-
)}
|
|
2465
2042
|
</div>
|
|
2466
2043
|
</div>
|
|
2467
2044
|
</div>
|