@flamingo-stack/openframe-frontend-core 0.0.219 → 0.0.220-snapshot.20260602172647
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-F3FO2ZZZ.cjs → chunk-4PBV66HQ.cjs} +7 -7
- package/dist/{chunk-F3FO2ZZZ.cjs.map → chunk-4PBV66HQ.cjs.map} +1 -1
- package/dist/{chunk-EDW2NVRV.js → chunk-4WZOFD46.js} +37 -37
- package/dist/{chunk-EDW2NVRV.js.map → chunk-4WZOFD46.js.map} +1 -1
- package/dist/{chunk-YX3YQNC4.cjs → chunk-73YDB6AT.cjs} +13 -13
- package/dist/{chunk-YX3YQNC4.cjs.map → chunk-73YDB6AT.cjs.map} +1 -1
- package/dist/{chunk-MPHDM2VZ.cjs → chunk-7TQNW2AM.cjs} +30 -30
- package/dist/{chunk-MPHDM2VZ.cjs.map → chunk-7TQNW2AM.cjs.map} +1 -1
- package/dist/{chunk-65CPJ4SX.cjs → chunk-C6ASEPZL.cjs} +30 -30
- package/dist/{chunk-65CPJ4SX.cjs.map → chunk-C6ASEPZL.cjs.map} +1 -1
- package/dist/{chunk-DRPECAXO.js → chunk-CPIX5AAR.js} +2 -2
- package/dist/{chunk-SRA2QYK6.js → chunk-E2AWBQEU.js} +4 -4
- package/dist/{chunk-SZXKKEUH.cjs → chunk-E6B4B7GM.cjs} +46 -30
- package/dist/chunk-E6B4B7GM.cjs.map +1 -0
- package/dist/{chunk-XG7DFRJL.js → chunk-FOOQFRJR.js} +3 -3
- package/dist/{chunk-A3PL6ZCF.js → chunk-IS4IZC7N.js} +6388 -5128
- package/dist/chunk-IS4IZC7N.js.map +1 -0
- package/dist/{chunk-ZGBXHK26.cjs → chunk-JMGSJHFP.cjs} +12 -12
- package/dist/{chunk-ZGBXHK26.cjs.map → chunk-JMGSJHFP.cjs.map} +1 -1
- package/dist/{chunk-SL3RGBPX.cjs → chunk-QNYH3WUU.cjs} +9 -9
- package/dist/{chunk-SL3RGBPX.cjs.map → chunk-QNYH3WUU.cjs.map} +1 -1
- package/dist/{chunk-24Q2WLIU.js → chunk-QYRV6MKX.js} +2 -2
- package/dist/{chunk-ZII7TNVA.js → chunk-SRCEVQYA.js} +3 -3
- package/dist/{chunk-Y3MXGCOW.js → chunk-YZDUOUMB.js} +46 -30
- package/dist/chunk-YZDUOUMB.js.map +1 -0
- package/dist/{chunk-7UZLRI7W.cjs → chunk-ZAGQXSAP.cjs} +3292 -2032
- package/dist/chunk-ZAGQXSAP.cjs.map +1 -0
- package/dist/components/chat/chat-archive-page.d.ts +25 -0
- package/dist/components/chat/chat-archive-page.d.ts.map +1 -0
- package/dist/components/chat/chat-composer.d.ts +29 -0
- package/dist/components/chat/chat-composer.d.ts.map +1 -0
- package/dist/components/chat/chat-header-icon-button.d.ts +14 -0
- package/dist/components/chat/chat-header-icon-button.d.ts.map +1 -0
- package/dist/components/chat/chat-panel-header.d.ts +32 -0
- package/dist/components/chat/chat-panel-header.d.ts.map +1 -0
- package/dist/components/chat/embeddable-chat.d.ts +18 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/guide-mode-banner.d.ts +16 -0
- package/dist/components/chat/guide-mode-banner.d.ts.map +1 -0
- package/dist/components/chat/guide-welcome.d.ts +42 -0
- package/dist/components/chat/guide-welcome.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts +58 -0
- package/dist/components/chat/hooks/use-chat-dialog-manager.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +26 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +29 -5
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +9 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +28 -4
- package/dist/components/chat/mingo-chat-history.d.ts +37 -0
- package/dist/components/chat/mingo-chat-history.d.ts.map +1 -0
- package/dist/components/chat/mingo-chat-modals.d.ts +50 -0
- package/dist/components/chat/mingo-chat-modals.d.ts.map +1 -0
- package/dist/components/chat/mingo-onboarding-card.d.ts.map +1 -1
- package/dist/components/chat/mingo-welcome.d.ts +78 -0
- package/dist/components/chat/mingo-welcome.d.ts.map +1 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts +6 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +6 -6
- package/dist/components/contact/index.js +5 -5
- package/dist/components/features/index.cjs +5 -5
- package/dist/components/features/index.js +4 -4
- package/dist/components/icons-v2-generated/brand-logos/fleet-mdm-logo-grey-icon.d.ts.map +1 -1
- package/dist/components/icons-v2-generated/brand-logos/fleet-mdm-logo-icon.d.ts.map +1 -1
- package/dist/components/icons-v2-generated/index.cjs +2 -2
- package/dist/components/icons-v2-generated/index.js +1 -1
- package/dist/components/index.cjs +128 -104
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +31 -7
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/page-heading.d.ts +7 -6
- package/dist/components/layout/page-heading.d.ts.map +1 -1
- package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
- package/dist/components/navigation/header-mingo-button.d.ts +4 -2
- package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +5 -5
- package/dist/components/navigation/index.js +4 -4
- package/dist/components/onboarding-guides/index.cjs +28 -28
- package/dist/components/onboarding-guides/index.js +6 -6
- package/dist/components/tickets/index.cjs +88 -88
- package/dist/components/tickets/index.js +7 -7
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +50 -50
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +29 -5
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +28 -4
- package/dist/components/ui/modal-v2.d.ts.map +1 -1
- package/dist/components/ui/more-actions-menu.d.ts +8 -1
- package/dist/components/ui/more-actions-menu.d.ts.map +1 -1
- package/dist/components/ui/portal-container.d.ts +21 -0
- package/dist/components/ui/portal-container.d.ts.map +1 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +29 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +28 -4
- package/package.json +1 -1
- package/src/components/chat/chat-archive-page.tsx +93 -0
- package/src/components/chat/chat-composer.tsx +99 -0
- package/src/components/chat/chat-header-icon-button.tsx +36 -0
- package/src/components/chat/chat-panel-header.tsx +114 -0
- package/src/components/chat/embeddable-chat.tsx +388 -311
- package/src/components/chat/guide-mode-banner.tsx +75 -0
- package/src/components/chat/guide-welcome.tsx +199 -0
- package/src/components/chat/hooks/use-chat-dialog-manager.ts +227 -0
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +85 -0
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +8 -0
- package/src/components/chat/hooks/use-unified-chat.ts +12 -0
- package/src/components/chat/index.ts +9 -0
- package/src/components/chat/mingo-chat-history.tsx +308 -0
- package/src/components/chat/mingo-chat-modals.tsx +223 -0
- package/src/components/chat/mingo-onboarding-card.tsx +5 -8
- package/src/components/chat/mingo-welcome.tsx +396 -0
- package/src/components/chat/types/unified-chat-state.types.ts +8 -0
- package/src/components/icons-v2/brand-logos/fleet-mdm-logo-grey.svg +6 -6
- package/src/components/icons-v2/brand-logos/fleet-mdm-logo.svg +6 -6
- package/src/components/icons-v2-generated/brand-logos/fleet-mdm-logo-grey-icon.tsx +2 -22
- package/src/components/icons-v2-generated/brand-logos/fleet-mdm-logo-icon.tsx +22 -2
- package/src/components/layout/page-heading.tsx +13 -7
- package/src/components/navigation/app-header.tsx +12 -12
- package/src/components/navigation/app-layout-drawer.tsx +25 -15
- package/src/components/navigation/header-mingo-button.tsx +22 -7
- package/src/components/ui/dropdown-menu.tsx +9 -3
- package/src/components/ui/modal-v2.tsx +33 -3
- package/src/components/ui/more-actions-menu.tsx +15 -2
- package/src/components/ui/portal-container.tsx +28 -0
- package/src/components/ui/tooltip.tsx +9 -3
- package/src/stories/AppLayoutSidebar.stories.tsx +184 -0
- package/src/stories/EmbeddableChat.stories.tsx +114 -0
- package/src/stories/GuideWelcome.stories.tsx +124 -0
- package/src/stories/MingoChatModals.stories.tsx +82 -0
- package/src/stories/MingoWelcome.stories.tsx +119 -0
- package/dist/chunk-7UZLRI7W.cjs.map +0 -1
- package/dist/chunk-A3PL6ZCF.js.map +0 -1
- package/dist/chunk-SZXKKEUH.cjs.map +0 -1
- package/dist/chunk-Y3MXGCOW.js.map +0 -1
- /package/dist/{chunk-DRPECAXO.js.map → chunk-CPIX5AAR.js.map} +0 -0
- /package/dist/{chunk-SRA2QYK6.js.map → chunk-E2AWBQEU.js.map} +0 -0
- /package/dist/{chunk-XG7DFRJL.js.map → chunk-FOOQFRJR.js.map} +0 -0
- /package/dist/{chunk-24Q2WLIU.js.map → chunk-QYRV6MKX.js.map} +0 -0
- /package/dist/{chunk-ZII7TNVA.js.map → chunk-SRCEVQYA.js.map} +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { cn } from '../../utils/cn'
|
|
5
|
+
import { CompassIcon } from '../icons-v2-generated/map-and-travel/compass-icon'
|
|
6
|
+
import { QuestionCircleIcon } from '../icons-v2-generated/signs-and-symbols/question-circle-icon'
|
|
7
|
+
import {
|
|
8
|
+
Tooltip,
|
|
9
|
+
TooltipContent,
|
|
10
|
+
TooltipProvider,
|
|
11
|
+
TooltipTrigger,
|
|
12
|
+
} from '../ui/tooltip'
|
|
13
|
+
|
|
14
|
+
const DEFAULT_LABEL = 'Guide Mode Chat'
|
|
15
|
+
|
|
16
|
+
const DEFAULT_TOOLTIP =
|
|
17
|
+
'A quick session chat for exploring OpenFrame. Mingo answers how-to ' +
|
|
18
|
+
'questions and guides you through configuration, but won’t touch devices, ' +
|
|
19
|
+
'run scripts, or perform any actions.'
|
|
20
|
+
|
|
21
|
+
export interface GuideModeBannerProps {
|
|
22
|
+
/** Banner label (uppercased by the `text-h5` style). */
|
|
23
|
+
label?: React.ReactNode
|
|
24
|
+
/** Help-icon tooltip copy. `null` hides the help icon entirely. */
|
|
25
|
+
tooltip?: React.ReactNode | null
|
|
26
|
+
className?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Guide-mode indicator banner — Figma node `7532:328222`. A full-bleed accent
|
|
31
|
+
* (yellow) strip below the chat header: leading compass icon, the "Guide Mode
|
|
32
|
+
* Chat" label, and a trailing help icon whose tooltip explains the temporary,
|
|
33
|
+
* read-only nature of Guide mode.
|
|
34
|
+
*/
|
|
35
|
+
export function GuideModeBanner({
|
|
36
|
+
label = DEFAULT_LABEL,
|
|
37
|
+
tooltip = DEFAULT_TOOLTIP,
|
|
38
|
+
className,
|
|
39
|
+
}: GuideModeBannerProps) {
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
className={cn(
|
|
43
|
+
'flex h-8 w-full shrink-0 items-center gap-[var(--spacing-system-xs)] bg-ods-accent px-[var(--spacing-system-m)] py-[var(--spacing-system-xsf)]',
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
<CompassIcon size={16} className="shrink-0 text-ods-text-on-accent" />
|
|
48
|
+
<p className="flex-1 truncate text-h5 uppercase text-ods-text-on-accent">
|
|
49
|
+
{label}
|
|
50
|
+
</p>
|
|
51
|
+
{tooltip != null && (
|
|
52
|
+
<TooltipProvider delayDuration={0}>
|
|
53
|
+
<Tooltip>
|
|
54
|
+
<TooltipTrigger asChild>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
aria-label="About Guide Mode"
|
|
58
|
+
className="shrink-0 rounded-sm text-ods-text-on-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-text-on-accent"
|
|
59
|
+
>
|
|
60
|
+
<QuestionCircleIcon size={16} />
|
|
61
|
+
</button>
|
|
62
|
+
</TooltipTrigger>
|
|
63
|
+
<TooltipContent
|
|
64
|
+
side="bottom"
|
|
65
|
+
align="end"
|
|
66
|
+
className="max-w-[280px] text-h6 normal-case"
|
|
67
|
+
>
|
|
68
|
+
{tooltip}
|
|
69
|
+
</TooltipContent>
|
|
70
|
+
</Tooltip>
|
|
71
|
+
</TooltipProvider>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { cn } from '../../utils/cn'
|
|
5
|
+
import { MingoIcon } from '../icons'
|
|
6
|
+
import { Tag } from '../ui/tag'
|
|
7
|
+
import { MoreActionsMenu } from '../ui/more-actions-menu'
|
|
8
|
+
import { Ellipsis01Icon } from '../icons-v2-generated'
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/** A guide quick-action chip (e.g. "How to start"). */
|
|
15
|
+
export interface GuideQuickAction {
|
|
16
|
+
/** Stable React key. */
|
|
17
|
+
id: string
|
|
18
|
+
/** Chip label. */
|
|
19
|
+
label: string
|
|
20
|
+
/** Prompt text seeded into the composer on click. Defaults to `label`. */
|
|
21
|
+
prompt?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface GuideWelcomeProps {
|
|
25
|
+
/** Greeting heading. Defaults to "Guide Mode Chat". */
|
|
26
|
+
title?: React.ReactNode
|
|
27
|
+
/** Greeting sub-line. Defaults to the OpenFrame temporary-session copy. */
|
|
28
|
+
subtitle?: React.ReactNode
|
|
29
|
+
/** Quick-action chips — caller-provided; there are no built-in defaults, so
|
|
30
|
+
* the chip row is omitted entirely unless the host supplies actions. The
|
|
31
|
+
* first `maxVisibleQuickActions` render inline; the rest collapse under a
|
|
32
|
+
* trailing "⋯" overflow menu. */
|
|
33
|
+
quickActions?: ReadonlyArray<GuideQuickAction>
|
|
34
|
+
/** How many chips to show inline before overflowing to the menu (default 3). */
|
|
35
|
+
maxVisibleQuickActions?: number
|
|
36
|
+
/** Fired when a quick-action chip (inline or menu) is activated. */
|
|
37
|
+
onQuickAction?: (action: GuideQuickAction) => void
|
|
38
|
+
/** Slash-command onboarding list — rendered inside the shared scroll region
|
|
39
|
+
* below the greeting (so greeting + list scroll together, with edge fades). */
|
|
40
|
+
children?: React.ReactNode
|
|
41
|
+
className?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// Defaults (OpenFrame copy — overridable; the kit stays platform-agnostic)
|
|
46
|
+
// =============================================================================
|
|
47
|
+
|
|
48
|
+
const DEFAULT_TITLE = 'Guide Mode Chat'
|
|
49
|
+
|
|
50
|
+
const DEFAULT_SUBTITLE =
|
|
51
|
+
'This chat is temporary and will not be saved. Ask about OpenFrame docs, ' +
|
|
52
|
+
'known issues, or manage your support tickets right here.'
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Component
|
|
56
|
+
// =============================================================================
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* GuideWelcome — Figma node `7532:328214`.
|
|
60
|
+
*
|
|
61
|
+
* Guide-mode chat empty state: a centred greeting and the slash-command
|
|
62
|
+
* onboarding list share one scroll region (with top/bottom fade affordances),
|
|
63
|
+
* and a pinned quick-action chip row sits above the composer. The first few
|
|
64
|
+
* quick actions render as chips; the remainder collapse under a "⋯" menu.
|
|
65
|
+
*
|
|
66
|
+
* Mirrors `MingoWelcome` (the default-mode empty state) — content is
|
|
67
|
+
* configurable with OpenFrame defaults.
|
|
68
|
+
*/
|
|
69
|
+
export function GuideWelcome({
|
|
70
|
+
title = DEFAULT_TITLE,
|
|
71
|
+
subtitle = DEFAULT_SUBTITLE,
|
|
72
|
+
quickActions = [],
|
|
73
|
+
maxVisibleQuickActions = 3,
|
|
74
|
+
onQuickAction,
|
|
75
|
+
children,
|
|
76
|
+
className,
|
|
77
|
+
}: GuideWelcomeProps) {
|
|
78
|
+
// Scroll-fade affordances: a 48px gradient at the top/bottom edge of the
|
|
79
|
+
// scroll region, shown only while content is actually hidden in that
|
|
80
|
+
// direction. (Same behaviour as MingoWelcome.)
|
|
81
|
+
const scrollRef = React.useRef<HTMLDivElement>(null)
|
|
82
|
+
const [scrollFade, setScrollFade] = React.useState({ top: false, bottom: false })
|
|
83
|
+
const updateScrollFade = React.useCallback(() => {
|
|
84
|
+
const el = scrollRef.current
|
|
85
|
+
if (!el) return
|
|
86
|
+
const top = el.scrollTop > 1
|
|
87
|
+
const bottom = el.scrollTop + el.clientHeight < el.scrollHeight - 1
|
|
88
|
+
setScrollFade((prev) =>
|
|
89
|
+
prev.top === top && prev.bottom === bottom ? prev : { top, bottom },
|
|
90
|
+
)
|
|
91
|
+
}, [])
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
updateScrollFade()
|
|
94
|
+
const el = scrollRef.current
|
|
95
|
+
if (!el || typeof ResizeObserver === 'undefined') return
|
|
96
|
+
const ro = new ResizeObserver(updateScrollFade)
|
|
97
|
+
ro.observe(el)
|
|
98
|
+
return () => ro.disconnect()
|
|
99
|
+
}, [updateScrollFade])
|
|
100
|
+
|
|
101
|
+
const visibleActions = quickActions.slice(0, maxVisibleQuickActions)
|
|
102
|
+
const overflowActions = quickActions.slice(maxVisibleQuickActions)
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
className={cn(
|
|
107
|
+
'flex flex-1 min-h-0 flex-col gap-[var(--spacing-system-m)]',
|
|
108
|
+
className,
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
111
|
+
{/* Greeting + slash-command list share one scroll region; `relative` so
|
|
112
|
+
the edge fades can overlay it. */}
|
|
113
|
+
<div className="relative flex flex-1 min-h-0 flex-col">
|
|
114
|
+
<div
|
|
115
|
+
ref={scrollRef}
|
|
116
|
+
onScroll={updateScrollFade}
|
|
117
|
+
className="flex flex-1 min-h-0 flex-col gap-[var(--spacing-system-m)] overflow-y-auto"
|
|
118
|
+
>
|
|
119
|
+
{/* Greeting grows to fill (`flex-1`) so it centres vertically while
|
|
120
|
+
the list stays anchored below it. */}
|
|
121
|
+
<div className="flex flex-1 flex-col items-center justify-center gap-[var(--spacing-system-l)] px-[var(--spacing-system-l)] py-[var(--spacing-system-xxl)] text-center">
|
|
122
|
+
<MingoIcon
|
|
123
|
+
className="h-12 w-12"
|
|
124
|
+
color="white"
|
|
125
|
+
eyesColor="var(--ods-flamingo-cyan-base)"
|
|
126
|
+
cornerColor="var(--ods-flamingo-cyan-base)"
|
|
127
|
+
/>
|
|
128
|
+
<div className="flex w-full flex-col gap-1">
|
|
129
|
+
<p className="text-h4 text-ods-text-primary">{title}</p>
|
|
130
|
+
<p className="text-h6 text-ods-text-secondary">{subtitle}</p>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{children}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{/* Top scroll-fade — visible only when content is hidden above. */}
|
|
138
|
+
<div
|
|
139
|
+
aria-hidden
|
|
140
|
+
className={cn(
|
|
141
|
+
'pointer-events-none absolute inset-x-0 top-0 h-12 transition-opacity duration-150',
|
|
142
|
+
scrollFade.top ? 'opacity-100' : 'opacity-0',
|
|
143
|
+
)}
|
|
144
|
+
style={{
|
|
145
|
+
background:
|
|
146
|
+
'linear-gradient(0deg, transparent 0%, var(--color-bg) 100%)',
|
|
147
|
+
}}
|
|
148
|
+
/>
|
|
149
|
+
{/* Bottom scroll-fade — visible only when content is hidden below. */}
|
|
150
|
+
<div
|
|
151
|
+
aria-hidden
|
|
152
|
+
className={cn(
|
|
153
|
+
'pointer-events-none absolute inset-x-0 bottom-0 h-12 transition-opacity duration-150',
|
|
154
|
+
scrollFade.bottom ? 'opacity-100' : 'opacity-0',
|
|
155
|
+
)}
|
|
156
|
+
style={{
|
|
157
|
+
background:
|
|
158
|
+
'linear-gradient(180deg, transparent 0%, var(--color-bg) 100%)',
|
|
159
|
+
}}
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
{/* Pinned quick-action chips — always visible above the composer. */}
|
|
164
|
+
{quickActions.length > 0 && (
|
|
165
|
+
<div className="flex shrink-0 flex-wrap items-center gap-1">
|
|
166
|
+
{visibleActions.map((action) => (
|
|
167
|
+
<button
|
|
168
|
+
key={action.id}
|
|
169
|
+
type="button"
|
|
170
|
+
onClick={() => onQuickAction?.(action)}
|
|
171
|
+
className="rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
|
|
172
|
+
>
|
|
173
|
+
<Tag variant="outline" label={action.label} />
|
|
174
|
+
</button>
|
|
175
|
+
))}
|
|
176
|
+
{overflowActions.length > 0 && (
|
|
177
|
+
<MoreActionsMenu
|
|
178
|
+
ariaLabel="More quick actions"
|
|
179
|
+
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
180
|
+
items={overflowActions.map((action) => ({
|
|
181
|
+
label: action.label,
|
|
182
|
+
onClick: () => onQuickAction?.(action),
|
|
183
|
+
}))}
|
|
184
|
+
trigger={
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
aria-label="More quick actions"
|
|
188
|
+
className="rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
|
|
189
|
+
>
|
|
190
|
+
<Tag variant="outline" label={<Ellipsis01Icon size={16} />} />
|
|
191
|
+
</button>
|
|
192
|
+
}
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo, useState } from 'react'
|
|
4
|
+
import type { DialogItem } from '../types/component.types'
|
|
5
|
+
import type {
|
|
6
|
+
FetchDialogsParams,
|
|
7
|
+
FetchDialogsResult,
|
|
8
|
+
} from './use-nats-chat-adapter'
|
|
9
|
+
|
|
10
|
+
export interface UseChatDialogManagerArgs {
|
|
11
|
+
/** Active (non-archived) dialogs from the unified chat state. */
|
|
12
|
+
dialogs: ReadonlyArray<DialogItem>
|
|
13
|
+
/** Currently-open dialog id. */
|
|
14
|
+
activeDialogId: string | null | undefined
|
|
15
|
+
/** Open a dialog by id. */
|
|
16
|
+
selectDialog: (id: string | null) => void
|
|
17
|
+
/** Reset the open conversation (drops back to the list / empty state). */
|
|
18
|
+
clearMessages: () => void
|
|
19
|
+
/** Rename a dialog (wired through the active adapter). */
|
|
20
|
+
renameDialog: (id: string, title: string) => Promise<void>
|
|
21
|
+
/** Archive a dialog (wired through the active adapter). */
|
|
22
|
+
archiveDialog: (id: string) => Promise<void>
|
|
23
|
+
/** Host callback that pages archived dialogs — gates the archive UI. */
|
|
24
|
+
fetchArchivedDialogs?: (
|
|
25
|
+
params: FetchDialogsParams,
|
|
26
|
+
) => Promise<FetchDialogsResult>
|
|
27
|
+
/** Host callback that restores an archived dialog — gates the restore UI. */
|
|
28
|
+
unarchiveDialog?: (id: string) => Promise<void>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Owns the chat panel's dialog-history concerns so `EmbeddableChat` stays an
|
|
33
|
+
* orchestrator rather than a state dump:
|
|
34
|
+
* - the Chat Archive page (open / paginate the archived list),
|
|
35
|
+
* - read-only "archived conversation" mode (open from the archive, restore,
|
|
36
|
+
* back-to-archive navigation),
|
|
37
|
+
* - the Rename / Archive / Unarchive confirmation-modal targets + handlers.
|
|
38
|
+
*
|
|
39
|
+
* Returns flat fields (intentionally the same names the JSX already uses) so
|
|
40
|
+
* the consuming component reads as a thin wiring layer.
|
|
41
|
+
*/
|
|
42
|
+
export function useChatDialogManager({
|
|
43
|
+
dialogs,
|
|
44
|
+
activeDialogId,
|
|
45
|
+
selectDialog,
|
|
46
|
+
clearMessages,
|
|
47
|
+
renameDialog,
|
|
48
|
+
archiveDialog,
|
|
49
|
+
fetchArchivedDialogs,
|
|
50
|
+
unarchiveDialog,
|
|
51
|
+
}: UseChatDialogManagerArgs) {
|
|
52
|
+
// ─── Rename / Archive confirmation modals ───
|
|
53
|
+
const [renameTarget, setRenameTarget] = useState<DialogItem | null>(null)
|
|
54
|
+
const [archiveTarget, setArchiveTarget] = useState<DialogItem | null>(null)
|
|
55
|
+
const handleConfirmRename = useCallback(
|
|
56
|
+
(name: string) => {
|
|
57
|
+
if (renameTarget) void renameDialog(renameTarget.id, name)
|
|
58
|
+
setRenameTarget(null)
|
|
59
|
+
},
|
|
60
|
+
[renameTarget, renameDialog],
|
|
61
|
+
)
|
|
62
|
+
const handleConfirmArchive = useCallback(async () => {
|
|
63
|
+
if (archiveTarget) {
|
|
64
|
+
try {
|
|
65
|
+
await archiveDialog(archiveTarget.id)
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// Keep the modal open (don't clear the target) so the user can retry;
|
|
68
|
+
// don't tear down the open conversation for an archive that failed.
|
|
69
|
+
console.error('[useChatDialogManager] archive failed:', err)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
// Archiving the open conversation drops back to the chat list.
|
|
73
|
+
if (archiveTarget.id === activeDialogId) {
|
|
74
|
+
setOpeningDialogId(null)
|
|
75
|
+
clearMessages()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
setArchiveTarget(null)
|
|
79
|
+
}, [archiveTarget, archiveDialog, activeDialogId, clearMessages])
|
|
80
|
+
|
|
81
|
+
// ─── Chat Archive page ───
|
|
82
|
+
// The archived list is paged locally (it's a secondary, on-demand view).
|
|
83
|
+
const [archiveOpen, setArchiveOpen] = useState(false)
|
|
84
|
+
const [archivedDialogs, setArchivedDialogs] = useState<DialogItem[]>([])
|
|
85
|
+
const [archivedCursor, setArchivedCursor] = useState<string | null>(null)
|
|
86
|
+
const [archivedLoading, setArchivedLoading] = useState(false)
|
|
87
|
+
const loadArchivedPage = useCallback(
|
|
88
|
+
async (cursor?: string): Promise<void> => {
|
|
89
|
+
if (!fetchArchivedDialogs) return
|
|
90
|
+
setArchivedLoading(true)
|
|
91
|
+
try {
|
|
92
|
+
const result = await fetchArchivedDialogs({ cursor, limit: 20 })
|
|
93
|
+
setArchivedCursor(result.nextCursor)
|
|
94
|
+
setArchivedDialogs((prev) =>
|
|
95
|
+
cursor ? [...prev, ...result.dialogs] : result.dialogs,
|
|
96
|
+
)
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error('[useChatDialogManager] fetchArchivedDialogs failed:', err)
|
|
99
|
+
} finally {
|
|
100
|
+
setArchivedLoading(false)
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
[fetchArchivedDialogs],
|
|
104
|
+
)
|
|
105
|
+
const openArchive = useCallback(() => {
|
|
106
|
+
setArchiveOpen(true)
|
|
107
|
+
setArchivedDialogs([])
|
|
108
|
+
setArchivedCursor(null)
|
|
109
|
+
void loadArchivedPage()
|
|
110
|
+
}, [loadArchivedPage])
|
|
111
|
+
const closeArchive = useCallback(() => setArchiveOpen(false), [])
|
|
112
|
+
|
|
113
|
+
// ─── Read-only "archived conversation" mode ───
|
|
114
|
+
// Set when a chat is opened FROM the archive page — flips the open
|
|
115
|
+
// conversation into read-only mode (restore button + disabled composer).
|
|
116
|
+
const [viewingArchivedId, setViewingArchivedId] = useState<string | null>(
|
|
117
|
+
null,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
// ─── Open-conversation tracking ───
|
|
121
|
+
// `openingDialogId` is set synchronously the moment a dialog is selected from
|
|
122
|
+
// the current-chats list, so the panel can flip to the conversation surface
|
|
123
|
+
// immediately — matching the archived-chat open (`viewingArchivedId` above)
|
|
124
|
+
// instead of waiting for `messages` to finish loading. Without it the normal
|
|
125
|
+
// open lagged behind the archived one (grey → load → flip vs. instant flip).
|
|
126
|
+
const [openingDialogId, setOpeningDialogId] = useState<string | null>(null)
|
|
127
|
+
const handleSelectDialog = useCallback(
|
|
128
|
+
(id: string) => {
|
|
129
|
+
setViewingArchivedId(null)
|
|
130
|
+
setOpeningDialogId(id)
|
|
131
|
+
selectDialog(id)
|
|
132
|
+
},
|
|
133
|
+
[selectDialog],
|
|
134
|
+
)
|
|
135
|
+
const isOpeningDialog =
|
|
136
|
+
openingDialogId != null && openingDialogId === activeDialogId
|
|
137
|
+
|
|
138
|
+
const handleArchivedSelect = useCallback(
|
|
139
|
+
(id: string) => {
|
|
140
|
+
setArchiveOpen(false)
|
|
141
|
+
setOpeningDialogId(null)
|
|
142
|
+
setViewingArchivedId(id)
|
|
143
|
+
selectDialog(id)
|
|
144
|
+
},
|
|
145
|
+
[selectDialog],
|
|
146
|
+
)
|
|
147
|
+
const [restoreTarget, setRestoreTarget] = useState<DialogItem | null>(null)
|
|
148
|
+
const handleConfirmRestore = useCallback(async () => {
|
|
149
|
+
if (restoreTarget) {
|
|
150
|
+
try {
|
|
151
|
+
await unarchiveDialog?.(restoreTarget.id)
|
|
152
|
+
} catch (err) {
|
|
153
|
+
// Backend restore failed — leave the local archived cache untouched so
|
|
154
|
+
// the dialog doesn't vanish from the archive only to resurface on the
|
|
155
|
+
// next fetch. Keep the modal open for a retry.
|
|
156
|
+
console.error('[useChatDialogManager] unarchive failed:', err)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
// Drop it from the locally-cached archived list and exit read-only mode
|
|
160
|
+
// so the composer returns and the user can keep chatting.
|
|
161
|
+
setArchivedDialogs((prev) => prev.filter((d) => d.id !== restoreTarget.id))
|
|
162
|
+
setViewingArchivedId((cur) => (cur === restoreTarget.id ? null : cur))
|
|
163
|
+
}
|
|
164
|
+
setRestoreTarget(null)
|
|
165
|
+
}, [restoreTarget, unarchiveDialog])
|
|
166
|
+
|
|
167
|
+
const isViewingArchived =
|
|
168
|
+
viewingArchivedId != null && viewingArchivedId === activeDialogId
|
|
169
|
+
|
|
170
|
+
// Header back-chevron: from an archived chat, return to the Chat Archive
|
|
171
|
+
// page (not the current-chats list); otherwise reset to the list.
|
|
172
|
+
const handleBack = useCallback(() => {
|
|
173
|
+
const wasArchived = isViewingArchived
|
|
174
|
+
setViewingArchivedId(null)
|
|
175
|
+
setOpeningDialogId(null)
|
|
176
|
+
// Reset the adapter's active dialog id (not just the message buffer):
|
|
177
|
+
// `clearMessages` empties `messages` but leaves `activeDialogId` pointing at
|
|
178
|
+
// the just-closed dialog, so re-selecting that same dialog from the list is
|
|
179
|
+
// a no-op the history-load effect skips → a blank conversation. Selecting
|
|
180
|
+
// `null` lets the next selection register as a real change and reload.
|
|
181
|
+
selectDialog(null)
|
|
182
|
+
clearMessages()
|
|
183
|
+
if (wasArchived) setArchiveOpen(true)
|
|
184
|
+
}, [isViewingArchived, selectDialog, clearMessages])
|
|
185
|
+
|
|
186
|
+
// Active-conversation dialog — resolved from the active list, or the
|
|
187
|
+
// archived list when an archived chat is open (archived dialogs aren't in
|
|
188
|
+
// `dialogs`, so the header title and ⋯ / restore actions need this).
|
|
189
|
+
const activeDialog = useMemo(
|
|
190
|
+
() =>
|
|
191
|
+
dialogs.find((d) => d.id === activeDialogId) ??
|
|
192
|
+
archivedDialogs.find((d) => d.id === activeDialogId),
|
|
193
|
+
[dialogs, archivedDialogs, activeDialogId],
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
// capability passthroughs (gate the archive / restore affordances)
|
|
198
|
+
fetchArchivedDialogs,
|
|
199
|
+
unarchiveDialog,
|
|
200
|
+
// archive page
|
|
201
|
+
archiveOpen,
|
|
202
|
+
archivedDialogs,
|
|
203
|
+
archivedCursor,
|
|
204
|
+
archivedLoading,
|
|
205
|
+
openArchive,
|
|
206
|
+
closeArchive,
|
|
207
|
+
loadArchivedPage,
|
|
208
|
+
handleArchivedSelect,
|
|
209
|
+
// open conversation (current-chats list)
|
|
210
|
+
handleSelectDialog,
|
|
211
|
+
isOpeningDialog,
|
|
212
|
+
// archived conversation
|
|
213
|
+
isViewingArchived,
|
|
214
|
+
handleBack,
|
|
215
|
+
activeDialog,
|
|
216
|
+
// action modals
|
|
217
|
+
renameTarget,
|
|
218
|
+
setRenameTarget,
|
|
219
|
+
archiveTarget,
|
|
220
|
+
setArchiveTarget,
|
|
221
|
+
restoreTarget,
|
|
222
|
+
setRestoreTarget,
|
|
223
|
+
handleConfirmRename,
|
|
224
|
+
handleConfirmArchive,
|
|
225
|
+
handleConfirmRestore,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -191,6 +191,19 @@ export interface UseNatsChatAdapterConfig {
|
|
|
191
191
|
*/
|
|
192
192
|
fetchChunks?: FetchChunksFunction
|
|
193
193
|
|
|
194
|
+
/**
|
|
195
|
+
* NATS topics to live-tail for the active dialog. Each maps to the
|
|
196
|
+
* subject suffix `chat.{dialogId}.{topic}` (see
|
|
197
|
+
* `useNatsDialogSubscription`). Defaults to `['message']` — the
|
|
198
|
+
* client-chat subject the Tauri Fae Chat consumer relies on. Admin /
|
|
199
|
+
* Mingo chat publishes its agent replies on `'admin-message'`, so the
|
|
200
|
+
* openframe EmbeddableChat host MUST set `topics: ['admin-message']`
|
|
201
|
+
* here — otherwise the subscription tails the wrong subject, no reply
|
|
202
|
+
* chunks ever arrive, and the assistant placeholder hangs forever in
|
|
203
|
+
* the `thinking` phase.
|
|
204
|
+
*/
|
|
205
|
+
topics?: NatsMessageType[]
|
|
206
|
+
|
|
194
207
|
/**
|
|
195
208
|
* Whether THINKING chunks are surfaced as segments. Default `false`
|
|
196
209
|
* (parity with the existing `useRealtimeChunkProcessor` default).
|
|
@@ -241,6 +254,25 @@ export interface UseNatsChatAdapterConfig {
|
|
|
241
254
|
* delete affordance is hidden. */
|
|
242
255
|
deleteDialog?: (dialogId: string) => Promise<void>
|
|
243
256
|
|
|
257
|
+
/** Rename a dialog on the backend. When omitted, the "Rename" affordance
|
|
258
|
+
* in the chat-history row menu is hidden. */
|
|
259
|
+
renameDialog?: (dialogId: string, title: string) => Promise<void>
|
|
260
|
+
|
|
261
|
+
/** Archive a dialog on the backend (removes it from the active list).
|
|
262
|
+
* When omitted, the "Archive" affordance in the row menu is hidden. */
|
|
263
|
+
archiveDialog?: (dialogId: string) => Promise<void>
|
|
264
|
+
|
|
265
|
+
/** Fetch a paginated page of ARCHIVED dialogs for the Chat Archive page.
|
|
266
|
+
* When omitted, the archive (clock-history) button in the header is
|
|
267
|
+
* hidden. Same page/cursor contract as `fetchDialogs`. */
|
|
268
|
+
fetchArchivedDialogs?: (
|
|
269
|
+
params: FetchDialogsParams,
|
|
270
|
+
) => Promise<FetchDialogsResult>
|
|
271
|
+
|
|
272
|
+
/** Restore an archived dialog back to the active list. When omitted, the
|
|
273
|
+
* restore (refresh) button in an archived chat's header is hidden. */
|
|
274
|
+
unarchiveDialog?: (dialogId: string) => Promise<void>
|
|
275
|
+
|
|
244
276
|
/**
|
|
245
277
|
* Approve a pending tool-call request. Wired into the segment
|
|
246
278
|
* accumulator's `onApprove` so approval-card buttons fire this
|
|
@@ -383,12 +415,15 @@ export function useNatsChatAdapter(
|
|
|
383
415
|
clientConfig,
|
|
384
416
|
publishUserMessage,
|
|
385
417
|
fetchChunks,
|
|
418
|
+
topics,
|
|
386
419
|
enableThinking,
|
|
387
420
|
batchApprovalsEnabled,
|
|
388
421
|
fetchDialogs,
|
|
389
422
|
fetchDialogMessages,
|
|
390
423
|
createDialog: createDialogCallback,
|
|
391
424
|
deleteDialog: deleteDialogCallback,
|
|
425
|
+
renameDialog: renameDialogCallback,
|
|
426
|
+
archiveDialog: archiveDialogCallback,
|
|
392
427
|
approveRequest: approveRequestCallback,
|
|
393
428
|
rejectRequest: rejectRequestCallback,
|
|
394
429
|
stopGeneration: stopGenerationCallback,
|
|
@@ -676,6 +711,10 @@ export function useNatsChatAdapter(
|
|
|
676
711
|
useNatsDialogSubscription({
|
|
677
712
|
enabled: active && dialogId != null,
|
|
678
713
|
dialogId,
|
|
714
|
+
// Subject suffix to tail. Omitted → the hook defaults to `['message']`
|
|
715
|
+
// (client chat). Admin/Mingo hosts pass `['admin-message']` so the
|
|
716
|
+
// live tail lands on the subject the agent actually publishes to.
|
|
717
|
+
...(topics ? { topics } : {}),
|
|
679
718
|
getNatsWsUrl,
|
|
680
719
|
clientConfig,
|
|
681
720
|
onEvent: (payload: unknown, messageType: NatsMessageType) => {
|
|
@@ -844,6 +883,48 @@ export function useNatsChatAdapter(
|
|
|
844
883
|
[deleteDialogCallback, internalDialogId, isManagedMode],
|
|
845
884
|
)
|
|
846
885
|
|
|
886
|
+
const renameDialog = useCallback(
|
|
887
|
+
async (id: string, title: string): Promise<void> => {
|
|
888
|
+
if (!renameDialogCallback) return
|
|
889
|
+
// Optimistic — update the local title immediately, roll back on error.
|
|
890
|
+
let previous: string | undefined
|
|
891
|
+
setDialogs((prev) =>
|
|
892
|
+
prev.map((d) => {
|
|
893
|
+
if (d.id !== id) return d
|
|
894
|
+
previous = d.title
|
|
895
|
+
return { ...d, title }
|
|
896
|
+
}),
|
|
897
|
+
)
|
|
898
|
+
try {
|
|
899
|
+
await renameDialogCallback(id, title)
|
|
900
|
+
} catch (err) {
|
|
901
|
+
console.error('[useNatsChatAdapter] renameDialog failed:', err)
|
|
902
|
+
if (previous !== undefined) {
|
|
903
|
+
setDialogs((prev) =>
|
|
904
|
+
prev.map((d) => (d.id === id ? { ...d, title: previous! } : d)),
|
|
905
|
+
)
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
[renameDialogCallback],
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
const archiveDialog = useCallback(
|
|
913
|
+
async (id: string): Promise<void> => {
|
|
914
|
+
if (!archiveDialogCallback) return
|
|
915
|
+
try {
|
|
916
|
+
await archiveDialogCallback(id)
|
|
917
|
+
setDialogs((prev) => prev.filter((d) => d.id !== id))
|
|
918
|
+
if (isManagedMode && internalDialogId === id) {
|
|
919
|
+
setInternalDialogId(null)
|
|
920
|
+
}
|
|
921
|
+
} catch (err) {
|
|
922
|
+
console.error('[useNatsChatAdapter] archiveDialog failed:', err)
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
[archiveDialogCallback, internalDialogId, isManagedMode],
|
|
926
|
+
)
|
|
927
|
+
|
|
847
928
|
const loadMoreDialogs = useCallback(async (): Promise<void> => {
|
|
848
929
|
if (!dialogsNextCursor) return
|
|
849
930
|
await loadDialogsPage(dialogsNextCursor)
|
|
@@ -906,6 +987,8 @@ export function useNatsChatAdapter(
|
|
|
906
987
|
selectDialog,
|
|
907
988
|
startNewDialog,
|
|
908
989
|
deleteDialog,
|
|
990
|
+
renameDialog,
|
|
991
|
+
archiveDialog,
|
|
909
992
|
isDialogsLoading: isDialogsLoading || isCreatingDialog,
|
|
910
993
|
isMessagesLoading,
|
|
911
994
|
hasMoreDialogs,
|
|
@@ -934,6 +1017,8 @@ export function useNatsChatAdapter(
|
|
|
934
1017
|
selectDialog,
|
|
935
1018
|
startNewDialog,
|
|
936
1019
|
deleteDialog,
|
|
1020
|
+
renameDialog,
|
|
1021
|
+
archiveDialog,
|
|
937
1022
|
isDialogsLoading,
|
|
938
1023
|
isCreatingDialog,
|
|
939
1024
|
isMessagesLoading,
|
|
@@ -1071,6 +1071,8 @@ export function useSseChatAdapter(
|
|
|
1071
1071
|
selectDialog: noopSelectDialog,
|
|
1072
1072
|
startNewDialog: noopStartNewDialog,
|
|
1073
1073
|
deleteDialog: noopDeleteDialog,
|
|
1074
|
+
renameDialog: noopRenameDialog,
|
|
1075
|
+
archiveDialog: noopArchiveDialog,
|
|
1074
1076
|
isDialogsLoading: false,
|
|
1075
1077
|
isMessagesLoading: false,
|
|
1076
1078
|
hasMoreDialogs: false,
|
|
@@ -1096,6 +1098,12 @@ const noopStartNewDialog = async (): Promise<string | null> => null
|
|
|
1096
1098
|
const noopDeleteDialog = async (_id: string): Promise<void> => {
|
|
1097
1099
|
/* no-op until Guide localStorage history is exposed */
|
|
1098
1100
|
}
|
|
1101
|
+
const noopRenameDialog = async (_id: string, _title: string): Promise<void> => {
|
|
1102
|
+
/* no-op until Guide localStorage history is exposed */
|
|
1103
|
+
}
|
|
1104
|
+
const noopArchiveDialog = async (_id: string): Promise<void> => {
|
|
1105
|
+
/* no-op until Guide localStorage history is exposed */
|
|
1106
|
+
}
|
|
1099
1107
|
const noopAsync = async (): Promise<void> => {
|
|
1100
1108
|
/* no-op pagination stub */
|
|
1101
1109
|
}
|
|
@@ -157,6 +157,14 @@ export function useUnifiedChat(
|
|
|
157
157
|
(id: string) => activeState.deleteDialog(id),
|
|
158
158
|
[activeState],
|
|
159
159
|
)
|
|
160
|
+
const renameDialog = useCallback(
|
|
161
|
+
(id: string, title: string) => activeState.renameDialog(id, title),
|
|
162
|
+
[activeState],
|
|
163
|
+
)
|
|
164
|
+
const archiveDialog = useCallback(
|
|
165
|
+
(id: string) => activeState.archiveDialog(id),
|
|
166
|
+
[activeState],
|
|
167
|
+
)
|
|
160
168
|
const loadMoreDialogs = useCallback(
|
|
161
169
|
() => activeState.loadMoreDialogs(),
|
|
162
170
|
[activeState],
|
|
@@ -198,6 +206,8 @@ export function useUnifiedChat(
|
|
|
198
206
|
selectDialog,
|
|
199
207
|
startNewDialog,
|
|
200
208
|
deleteDialog,
|
|
209
|
+
renameDialog,
|
|
210
|
+
archiveDialog,
|
|
201
211
|
isDialogsLoading: activeState.isDialogsLoading,
|
|
202
212
|
isMessagesLoading: activeState.isMessagesLoading,
|
|
203
213
|
hasMoreDialogs: activeState.hasMoreDialogs,
|
|
@@ -221,6 +231,8 @@ export function useUnifiedChat(
|
|
|
221
231
|
selectDialog,
|
|
222
232
|
startNewDialog,
|
|
223
233
|
deleteDialog,
|
|
234
|
+
renameDialog,
|
|
235
|
+
archiveDialog,
|
|
224
236
|
loadMoreDialogs,
|
|
225
237
|
loadMoreMessages,
|
|
226
238
|
approveRequest,
|