@flamingo-stack/openframe-frontend-core 0.0.466 → 0.0.467-snapshot.20260723125044
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-5H3HACZZ.cjs → chunk-2WZAR2MB.cjs} +37 -37
- package/dist/{chunk-5H3HACZZ.cjs.map → chunk-2WZAR2MB.cjs.map} +1 -1
- package/dist/{chunk-FGL4ZNR7.cjs → chunk-3I34MUEG.cjs} +62 -62
- package/dist/{chunk-FGL4ZNR7.cjs.map → chunk-3I34MUEG.cjs.map} +1 -1
- package/dist/{chunk-VLLFRPNP.cjs → chunk-3PLQWO4H.cjs} +913 -742
- package/dist/chunk-3PLQWO4H.cjs.map +1 -0
- package/dist/{chunk-GRB7LPJV.js → chunk-3TU6QURP.js} +2 -2
- package/dist/{chunk-KX2TBXKN.js → chunk-AEM7ADDG.js} +2 -2
- package/dist/{chunk-RQZOCLTI.js → chunk-CLW2S55N.js} +4 -4
- package/dist/{chunk-YN77BNI7.cjs → chunk-ELIY4AO2.cjs} +26 -26
- package/dist/{chunk-YN77BNI7.cjs.map → chunk-ELIY4AO2.cjs.map} +1 -1
- package/dist/{chunk-FHLJQI4F.js → chunk-EZESVVIM.js} +4 -4
- package/dist/{chunk-7DULN25L.js → chunk-HTSWLPMU.js} +2 -2
- package/dist/{chunk-KLBBOLDK.cjs → chunk-HVLMAT4T.cjs} +5 -5
- package/dist/{chunk-KLBBOLDK.cjs.map → chunk-HVLMAT4T.cjs.map} +1 -1
- package/dist/{chunk-CC73CSLI.js → chunk-IUMPVRSH.js} +2 -2
- package/dist/{chunk-3WF7INYU.cjs → chunk-J6CE6HZS.cjs} +11 -11
- package/dist/{chunk-3WF7INYU.cjs.map → chunk-J6CE6HZS.cjs.map} +1 -1
- package/dist/{chunk-72HPXAMZ.js → chunk-JW3UNHGW.js} +2 -2
- package/dist/{chunk-BVNAYWLM.cjs → chunk-L3BWGFHT.cjs} +15 -15
- package/dist/{chunk-BVNAYWLM.cjs.map → chunk-L3BWGFHT.cjs.map} +1 -1
- package/dist/{chunk-NHNKMR22.cjs → chunk-MULVIGIN.cjs} +59 -59
- package/dist/{chunk-NHNKMR22.cjs.map → chunk-MULVIGIN.cjs.map} +1 -1
- package/dist/{chunk-OO7DO4DI.js → chunk-O2QOC5FA.js} +4 -4
- package/dist/{chunk-IZCOYFCN.js → chunk-P5SLNM3C.js} +5276 -5105
- package/dist/chunk-P5SLNM3C.js.map +1 -0
- package/dist/{chunk-WAO34PFO.js → chunk-QTGYQ2U7.js} +2 -2
- package/dist/{chunk-GHZMNDPN.js → chunk-QZJN5OHK.js} +4 -4
- package/dist/{chunk-HDP5VRPL.cjs → chunk-TISXEAGH.cjs} +7 -7
- package/dist/{chunk-HDP5VRPL.cjs.map → chunk-TISXEAGH.cjs.map} +1 -1
- package/dist/{chunk-2VCSDMPV.cjs → chunk-VHFH3BG6.cjs} +31 -31
- package/dist/{chunk-2VCSDMPV.cjs.map → chunk-VHFH3BG6.cjs.map} +1 -1
- package/dist/{chunk-MQQ7VIG6.cjs → chunk-VRJXY6TM.cjs} +26 -26
- package/dist/{chunk-MQQ7VIG6.cjs.map → chunk-VRJXY6TM.cjs.map} +1 -1
- package/dist/{chunk-XJH5ZT5Y.js → chunk-VYUEOKI6.js} +2 -2
- package/dist/{chunk-YHCLDRMU.cjs → chunk-X77X3RKH.cjs} +9 -9
- package/dist/{chunk-YHCLDRMU.cjs.map → chunk-X77X3RKH.cjs.map} +1 -1
- package/dist/components/announcement-bar.d.ts +1 -1
- package/dist/components/announcement-bar.d.ts.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-composer.d.ts +8 -1
- package/dist/components/chat/chat-composer.d.ts.map +1 -1
- package/dist/components/chat/chat-context-memory-bar.d.ts +37 -0
- package/dist/components/chat/chat-context-memory-bar.d.ts.map +1 -0
- package/dist/components/chat/embeddable-chat.d.ts +14 -6
- package/dist/components/chat/embeddable-chat.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 +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +3 -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/form.d.ts +1 -0
- package/dist/components/form.d.ts.map +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 +234 -206
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +119 -91
- 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/announcement-bar-view.d.ts +49 -0
- package/dist/components/ui/announcement-bar-view.d.ts.map +1 -0
- package/dist/components/ui/index.cjs +6 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +5 -1
- package/dist/index.cjs +6 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -1
- package/package.json +1 -1
- package/src/components/announcement-bar.tsx +102 -110
- package/src/components/chat/chat-composer.tsx +32 -5
- package/src/components/chat/chat-context-memory-bar.tsx +159 -0
- package/src/components/chat/embeddable-chat.tsx +17 -15
- package/src/components/chat/index.ts +1 -0
- package/src/components/ui/announcement-bar-view.tsx +94 -0
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/tab-selector.tsx +8 -8
- package/src/styles/ods-colors.css +16 -0
- package/dist/chunk-IZCOYFCN.js.map +0 -1
- package/dist/chunk-VLLFRPNP.cjs.map +0 -1
- /package/dist/{chunk-GRB7LPJV.js.map → chunk-3TU6QURP.js.map} +0 -0
- /package/dist/{chunk-KX2TBXKN.js.map → chunk-AEM7ADDG.js.map} +0 -0
- /package/dist/{chunk-RQZOCLTI.js.map → chunk-CLW2S55N.js.map} +0 -0
- /package/dist/{chunk-FHLJQI4F.js.map → chunk-EZESVVIM.js.map} +0 -0
- /package/dist/{chunk-7DULN25L.js.map → chunk-HTSWLPMU.js.map} +0 -0
- /package/dist/{chunk-CC73CSLI.js.map → chunk-IUMPVRSH.js.map} +0 -0
- /package/dist/{chunk-72HPXAMZ.js.map → chunk-JW3UNHGW.js.map} +0 -0
- /package/dist/{chunk-OO7DO4DI.js.map → chunk-O2QOC5FA.js.map} +0 -0
- /package/dist/{chunk-WAO34PFO.js.map → chunk-QTGYQ2U7.js.map} +0 -0
- /package/dist/{chunk-GHZMNDPN.js.map → chunk-QZJN5OHK.js.map} +0 -0
- /package/dist/{chunk-XJH5ZT5Y.js.map → chunk-VYUEOKI6.js.map} +0 -0
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
3
|
import { X } from 'lucide-react';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { useEndpointsRuntime } from '../contexts/endpoints-runtime-context';
|
|
6
|
+
import { useSelfFetch } from '../hooks/use-self-fetch';
|
|
7
|
+
import type { Announcement, AnnouncementBarProps, AnnouncementResponse } from '../types/announcement';
|
|
8
|
+
import { ANNOUNCEMENT_CTA_DEFAULTS } from '../types/announcement';
|
|
7
9
|
import {
|
|
10
|
+
clearLegacyAnnouncementCache,
|
|
8
11
|
dismissAnnouncement,
|
|
9
12
|
isAnnouncementDismissed,
|
|
10
|
-
clearLegacyAnnouncementCache,
|
|
11
13
|
} from '../utils/announcement-storage';
|
|
12
|
-
import { ANNOUNCEMENT_CTA_DEFAULTS } from '../types/announcement';
|
|
13
|
-
import type { Announcement, AnnouncementBarProps, AnnouncementResponse } from '../types/announcement';
|
|
14
14
|
import { getAppType } from '../utils/app-config';
|
|
15
|
-
import { useEndpointsRuntime } from '../contexts/endpoints-runtime-context';
|
|
16
|
-
import { useSelfFetch } from '../hooks/use-self-fetch';
|
|
17
15
|
import { pickReadableTextColor } from '../utils/color-analysis';
|
|
16
|
+
import { EntityIcon } from './icon-display';
|
|
17
|
+
import { AnnouncementBarView } from './ui/announcement-bar-view';
|
|
18
|
+
import { Button } from './ui/button';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Platform announcement bar — DUAL MODE, works with or without SSR.
|
|
@@ -41,11 +42,7 @@ import { pickReadableTextColor } from '../utils/color-analysis';
|
|
|
41
42
|
* storage reads happen ONLY in effects (a render-time read would desync
|
|
42
43
|
* hydration in SSR mode).
|
|
43
44
|
*/
|
|
44
|
-
export function AnnouncementBar({
|
|
45
|
-
initialAnnouncement,
|
|
46
|
-
previewMode = false,
|
|
47
|
-
className,
|
|
48
|
-
}: AnnouncementBarProps = {}) {
|
|
45
|
+
export function AnnouncementBar({ initialAnnouncement, previewMode = false, className }: AnnouncementBarProps = {}) {
|
|
49
46
|
// Namespace for the dismissal cookie/legacy keys. Next hosts inline
|
|
50
47
|
// NEXT_PUBLIC_APP_TYPE (matching the server's currentPlatform(), which the
|
|
51
48
|
// hub layout uses for the SSR cookie read); platform-agnostic embeds get
|
|
@@ -55,7 +52,7 @@ export function AnnouncementBar({
|
|
|
55
52
|
|
|
56
53
|
// Optional endpoint runtime: no provider → no URL → fetching disabled.
|
|
57
54
|
const endpoints = useEndpointsRuntime();
|
|
58
|
-
const url = previewMode ? null : endpoints?.announcementsUrl ?? null;
|
|
55
|
+
const url = previewMode ? null : (endpoints?.announcementsUrl ?? null);
|
|
59
56
|
|
|
60
57
|
// MUST be memoized (the hook re-syncs on [initialData] identity — an inline
|
|
61
58
|
// literal per render would setData-loop) and strict-undefined-mapped:
|
|
@@ -157,18 +154,20 @@ export function AnnouncementBar({
|
|
|
157
154
|
// dark-toned needs the dark theme's). `.theme-light` / `.theme-dark` are
|
|
158
155
|
// the design system's own scoping classes (ods-colors.css) and flip every
|
|
159
156
|
// Tier-1 `--ods-*` primitive for descendants.
|
|
160
|
-
const themeScope =
|
|
157
|
+
const themeScope =
|
|
158
|
+
pickReadableTextColor(displayAnnouncement.background_color) === 'dark' ? 'theme-light' : 'theme-dark';
|
|
161
159
|
|
|
162
|
-
// Inside the scope, colors reference Tier-
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
// theme
|
|
166
|
-
//
|
|
167
|
-
// ODS theme; nothing is hardcoded.
|
|
160
|
+
// Inside the scope, colors reference Tier-2 semantic aliases. Aliases
|
|
161
|
+
// declared only at :root would NOT re-resolve in a nested theme scope
|
|
162
|
+
// (they compute at :root), so ods-colors.css re-anchors these three inside
|
|
163
|
+
// `.theme-light` / `.theme-dark` specifically for theme islands like this
|
|
164
|
+
// bar. Every value comes from the active ODS theme; nothing is hardcoded.
|
|
168
165
|
const barButtonClasses =
|
|
169
|
-
'text-[color:var(--
|
|
166
|
+
'text-[color:var(--color-text-primary)] hover:bg-[var(--color-bg-hover)] active:bg-[var(--color-bg-active)]';
|
|
170
167
|
|
|
171
|
-
const hasCta = Boolean(
|
|
168
|
+
const hasCta = Boolean(
|
|
169
|
+
displayAnnouncement.cta_enabled && displayAnnouncement.cta_url && displayAnnouncement.cta_text,
|
|
170
|
+
);
|
|
172
171
|
|
|
173
172
|
return (
|
|
174
173
|
<div
|
|
@@ -184,32 +183,23 @@ export function AnnouncementBar({
|
|
|
184
183
|
style={{ gridTemplateRows: expanded ? '1fr' : '0fr' }}
|
|
185
184
|
>
|
|
186
185
|
{/*
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
186
|
+
Markup is the shared pure view (AnnouncementBarView, Figma
|
|
187
|
+
9364-40603 / 9418-43969 / 9418-44006): one row from `md` (800px) up,
|
|
188
|
+
stacked below it with the CTA stretched full-width next to the
|
|
189
|
+
content-width dismiss. The CTA Button is now rendered on EVERY
|
|
190
|
+
breakpoint (it replaced the old whole-bar mobile tap target, which
|
|
191
|
+
also retires the 768px window.innerWidth check that disagreed with
|
|
192
|
+
the md:800px breakpoint).
|
|
193
193
|
*/}
|
|
194
|
-
<div
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
onClick={(e) => {
|
|
204
|
-
// Only handle click on mobile (< 768px) and if CTA is enabled
|
|
205
|
-
if (window.innerWidth < 768 && hasCta) {
|
|
206
|
-
e.preventDefault();
|
|
207
|
-
handleCtaClick();
|
|
208
|
-
}
|
|
209
|
-
}}
|
|
210
|
-
>
|
|
211
|
-
{/* ONE unified icon path (shared with the chat): uploaded image URL
|
|
212
|
-
wins, else a library glyph by name (+ props), via <EntityIcon>. */}
|
|
194
|
+
<div
|
|
195
|
+
className={`min-h-0 overflow-hidden ${themeScope}`}
|
|
196
|
+
style={{ backgroundColor: displayAnnouncement.background_color }}
|
|
197
|
+
>
|
|
198
|
+
<AnnouncementBarView
|
|
199
|
+
className="text-[color:var(--color-text-primary)]"
|
|
200
|
+
startAdornment={
|
|
201
|
+
/* ONE unified icon path (shared with the chat): uploaded image URL
|
|
202
|
+
wins, else a library glyph by name (+ props), via <EntityIcon>. */
|
|
213
203
|
<EntityIcon
|
|
214
204
|
icon={{
|
|
215
205
|
name: displayAnnouncement.icon_name || 'openframe-logo',
|
|
@@ -217,72 +207,74 @@ export function AnnouncementBar({
|
|
|
217
207
|
props: displayAnnouncement.icon_props,
|
|
218
208
|
}}
|
|
219
209
|
size={24}
|
|
220
|
-
|
|
210
|
+
// `!` is required: logo glyphs (LogoOpenframeIcon / sizedLogo in
|
|
211
|
+
// icon-library) drive their size via an inline style, which beats
|
|
212
|
+
// a plain class — author !important beats the inline style, so
|
|
213
|
+
// the responsive token (16px < md, 24px from md) wins everywhere.
|
|
214
|
+
className="relative !size-[var(--icon-size-icon-size)] shrink-0"
|
|
221
215
|
/>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
216
|
+
}
|
|
217
|
+
title={
|
|
218
|
+
/* Single-line message: title + description inline, truncating as
|
|
219
|
+
one unit. Type per the ODS mockup (2862-8391): the "h3 body @
|
|
220
|
+
500" Figma style maps to the code's `.text-h4` utility (the
|
|
221
|
+
`.text-h3` utility is 700; h3/h4 body share the same size
|
|
222
|
+
tokens) — the same treatment string titles get from the view.
|
|
223
|
+
Description keeps opacity-80 for hierarchy. Separator is a
|
|
224
|
+
middot (house rule: no en/em dashes in copy). */
|
|
225
|
+
<p className="min-w-0 max-w-full text-h4 truncate mb-0">
|
|
226
|
+
<span>{displayAnnouncement.title}</span>
|
|
228
227
|
{displayAnnouncement.description && (
|
|
229
228
|
<span className="hidden sm:inline opacity-80"> · {displayAnnouncement.description}</span>
|
|
230
229
|
)}
|
|
231
230
|
</p>
|
|
231
|
+
}
|
|
232
|
+
actionBlock={
|
|
233
|
+
/* CTA - the common Button carrying the ADMIN-CONFIGURED colors
|
|
234
|
+
(cta_button_background_color / cta_button_text_color are an
|
|
235
|
+
admin FEATURE, defaults from ANNOUNCEMENT_CTA_DEFAULTS —
|
|
236
|
+
announcement colors are data, not token surfaces). Inline
|
|
237
|
+
styles win over the variant's hover classes on every state,
|
|
238
|
+
so hover feedback is opacity (the bar's original treatment);
|
|
239
|
+
nothing can render dark-on-dark.
|
|
232
240
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
>
|
|
270
|
-
{displayAnnouncement.cta_text}
|
|
271
|
-
</Button>
|
|
272
|
-
</div>
|
|
273
|
-
)}
|
|
274
|
-
</div>
|
|
275
|
-
|
|
276
|
-
{/* Dismiss - the common Button in its ghost-icon treatment
|
|
277
|
-
(size="icon-sm": 32px target, >= the 24px WCAG 2.5.8 AA floor,
|
|
278
|
-
16px glyph) with the bar's quiet tint hover. Inert in
|
|
279
|
-
previewMode. */}
|
|
280
|
-
<div className="flex-shrink-0 ml-1 md:ml-2 mr-2 md:mr-4">
|
|
241
|
+
Geometry + type come from the design system's size="compact"
|
|
242
|
+
(24px caption-scale pill for slim strips — rationale documented
|
|
243
|
+
on the variant in button.tsx). */
|
|
244
|
+
hasCta && displayAnnouncement.cta_text ? (
|
|
245
|
+
<Button
|
|
246
|
+
onClick={handleCtaClick}
|
|
247
|
+
variant="outline"
|
|
248
|
+
size="compact"
|
|
249
|
+
className="transition-opacity hover:opacity-90"
|
|
250
|
+
style={{
|
|
251
|
+
backgroundColor:
|
|
252
|
+
displayAnnouncement.cta_button_background_color || ANNOUNCEMENT_CTA_DEFAULTS.background,
|
|
253
|
+
color: displayAnnouncement.cta_button_text_color || ANNOUNCEMENT_CTA_DEFAULTS.text,
|
|
254
|
+
borderColor: displayAnnouncement.cta_button_background_color || ANNOUNCEMENT_CTA_DEFAULTS.background,
|
|
255
|
+
}}
|
|
256
|
+
tabIndex={expanded ? 0 : -1}
|
|
257
|
+
leftIcon={
|
|
258
|
+
displayAnnouncement.cta_show_icon && displayAnnouncement.cta_icon_name ? (
|
|
259
|
+
<EntityIcon
|
|
260
|
+
icon={{ name: displayAnnouncement.cta_icon_name, props: displayAnnouncement.cta_icon_props }}
|
|
261
|
+
size={14}
|
|
262
|
+
className="w-3.5 h-3.5"
|
|
263
|
+
/>
|
|
264
|
+
) : undefined
|
|
265
|
+
}
|
|
266
|
+
>
|
|
267
|
+
{displayAnnouncement.cta_text}
|
|
268
|
+
</Button>
|
|
269
|
+
) : undefined
|
|
270
|
+
}
|
|
271
|
+
endAdornment={
|
|
272
|
+
/* Dismiss - the common Button in its ghost-icon treatment
|
|
273
|
+
(size="icon-sm": 32px target, >= the 24px WCAG 2.5.8 AA floor,
|
|
274
|
+
16px glyph) with the bar's quiet tint hover. Inert in
|
|
275
|
+
previewMode. */
|
|
281
276
|
<Button
|
|
282
|
-
onClick={
|
|
283
|
-
e.stopPropagation(); // Prevent triggering the mobile CTA click
|
|
284
|
-
handleDismiss();
|
|
285
|
-
}}
|
|
277
|
+
onClick={handleDismiss}
|
|
286
278
|
variant="transparent"
|
|
287
279
|
size="icon-sm"
|
|
288
280
|
className={barButtonClasses}
|
|
@@ -292,8 +284,8 @@ export function AnnouncementBar({
|
|
|
292
284
|
>
|
|
293
285
|
<X strokeWidth={2} />
|
|
294
286
|
</Button>
|
|
295
|
-
|
|
296
|
-
|
|
287
|
+
}
|
|
288
|
+
/>
|
|
297
289
|
</div>
|
|
298
290
|
</div>
|
|
299
291
|
);
|
|
@@ -4,6 +4,7 @@ import * as React from 'react'
|
|
|
4
4
|
import { ChatFooter } from './chat-container'
|
|
5
5
|
import { ChatInput } from './chat-input'
|
|
6
6
|
import { ChatAttachmentAddButton } from './chat-attachment-bar'
|
|
7
|
+
import { ChatContextMemoryBar } from './chat-context-memory-bar'
|
|
7
8
|
import {
|
|
8
9
|
ChatComposerPlusMenu,
|
|
9
10
|
ChatContextChipStrip,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
} from './chat-context-picker'
|
|
12
13
|
import { ModelDisplay } from './model-display'
|
|
13
14
|
import { BoxArchiveIcon } from '../icons-v2-generated'
|
|
15
|
+
import { cn } from '../../utils/cn'
|
|
14
16
|
import type { ChatInputRef, ModelDisplayProps } from './types/component.types'
|
|
15
17
|
import type {
|
|
16
18
|
ChatContextItem,
|
|
@@ -49,6 +51,13 @@ export interface ChatComposerProps {
|
|
|
49
51
|
onToggleContextItem?: (item: ChatContextItem) => void
|
|
50
52
|
/** Remove an item (chip ×). */
|
|
51
53
|
onRemoveContextItem?: (item: ChatContextItem) => void
|
|
54
|
+
/** Ambient CONTEXT MEMORY (Figma 271:38656) — the entities the host collected
|
|
55
|
+
* from the user's navigation history and sends with every message. Rendered
|
|
56
|
+
* as a summary strip at the top of the composer card, above the assigned-item
|
|
57
|
+
* chips. Empty/omitted → no strip. */
|
|
58
|
+
contextMemoryItems?: ChatContextItem[]
|
|
59
|
+
/** Drop one entity from the context memory (dropdown row ×). */
|
|
60
|
+
onRemoveContextMemoryItem?: (item: ChatContextItem) => void
|
|
52
61
|
/** Controlled picker open state. */
|
|
53
62
|
contextPickerOpen?: boolean
|
|
54
63
|
/** Open the picker (the `+` menu's "Assign Item"). */
|
|
@@ -94,6 +103,8 @@ export function ChatComposer({
|
|
|
94
103
|
selectedContextItems,
|
|
95
104
|
onToggleContextItem,
|
|
96
105
|
onRemoveContextItem,
|
|
106
|
+
contextMemoryItems,
|
|
107
|
+
onRemoveContextMemoryItem,
|
|
97
108
|
contextPickerOpen = false,
|
|
98
109
|
onOpenContextPicker,
|
|
99
110
|
onCloseContextPicker,
|
|
@@ -104,6 +115,7 @@ export function ChatComposer({
|
|
|
104
115
|
}: ChatComposerProps) {
|
|
105
116
|
const contextEnabled = !!contextPicker && !archived
|
|
106
117
|
const selected = selectedContextItems ?? []
|
|
118
|
+
const memory = contextMemoryItems ?? []
|
|
107
119
|
|
|
108
120
|
// type → icon lookup so the chips (composer + bubble) lead with their
|
|
109
121
|
// entity-type glyph without the parent threading an extra resolver. Keyed on
|
|
@@ -184,23 +196,38 @@ export function ChatComposer({
|
|
|
184
196
|
</p>
|
|
185
197
|
</div>
|
|
186
198
|
) : contextEnabled ? (
|
|
187
|
-
// Unified card (Figma 1:6073):
|
|
188
|
-
// (`bg-ods-bg`,
|
|
189
|
-
//
|
|
190
|
-
//
|
|
199
|
+
// Unified card (Figma 1:6073 / 271:38656): the context-memory
|
|
200
|
+
// summary strip and the assigned-chip strip (both `bg-ods-bg`,
|
|
201
|
+
// `border-b`) stack flush above the input; the card owns the border
|
|
202
|
+
// / radius / focus ring. No `overflow-hidden` so the picker and the
|
|
203
|
+
// memory popover can float above the input (whichever strip comes
|
|
204
|
+
// first gets `rounded-t`).
|
|
191
205
|
// `has-[[data-editor]:focus]` (not `focus-within`) so the accent
|
|
192
206
|
// border reacts ONLY to the composer editor — not the picker's search
|
|
193
207
|
// input or item buttons, which also live inside this card. The editor
|
|
194
208
|
// is a `contentEditable` div (`data-editor`), not a `<textarea>`, so we
|
|
195
209
|
// target its data attribute rather than the element name.
|
|
196
210
|
<div className="rounded-md border border-ods-border bg-ods-card transition-colors has-[[data-editor]:focus]:border-ods-accent">
|
|
211
|
+
{/* Ambient navigation-history context — always the same items, so
|
|
212
|
+
it sits furthest from the textarea. Self-hides when empty. */}
|
|
213
|
+
<ChatContextMemoryBar
|
|
214
|
+
items={memory}
|
|
215
|
+
onRemove={onRemoveContextMemoryItem}
|
|
216
|
+
resolveIcon={resolveContextIcon}
|
|
217
|
+
disabled={sending}
|
|
218
|
+
className="rounded-t-md"
|
|
219
|
+
/>
|
|
197
220
|
{selected.length > 0 && (
|
|
198
221
|
<ChatContextChipStrip
|
|
199
222
|
items={selected}
|
|
200
223
|
onRemove={onRemoveContextItem}
|
|
201
224
|
resolveIcon={resolveContextIcon}
|
|
202
225
|
disabled={sending}
|
|
203
|
-
className=
|
|
226
|
+
className={cn(
|
|
227
|
+
'border-b border-ods-border bg-ods-bg p-2',
|
|
228
|
+
// Only the TOP strip rounds into the card corner.
|
|
229
|
+
memory.length === 0 && 'rounded-t-md',
|
|
230
|
+
)}
|
|
204
231
|
/>
|
|
205
232
|
)}
|
|
206
233
|
{/* Picker is rendered inside the `+` button (its `dropdown` prop)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<ChatContextMemoryBar>` — the composer's CONTEXT MEMORY strip
|
|
5
|
+
* (Figma node 271:38656).
|
|
6
|
+
*
|
|
7
|
+
* A one-line summary row pinned to the top of the composer card ("5 recently
|
|
8
|
+
* viewed items in context") with a `⋯` trigger that opens a dropdown listing
|
|
9
|
+
* every remembered entity, each removable via its `×`.
|
|
10
|
+
*
|
|
11
|
+
* This is the AMBIENT half of the composer's context UI — the entities the host
|
|
12
|
+
* accumulated from the user's navigation history and sends along with every
|
|
13
|
+
* message — as opposed to `<ChatContextChipStrip>`, which shows the items the
|
|
14
|
+
* user ASSIGNED to the message they're typing. Both sit above the input; memory
|
|
15
|
+
* on top (passive, always the same items), assigned chips closest to the
|
|
16
|
+
* textarea (active selection for this message).
|
|
17
|
+
*
|
|
18
|
+
* The lib owns only the chrome: items and removal are host-fed
|
|
19
|
+
* (`EmbeddableChat`'s `contextMemory` prop), and lead glyphs come from the
|
|
20
|
+
* picker's entity-type icon map via `resolveIcon`. Renders `null` when the host
|
|
21
|
+
* has nothing remembered, so it has no permanent footprint.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type * as React from 'react'
|
|
25
|
+
import { useEffect, useRef, useState } from 'react'
|
|
26
|
+
import { Ellipsis01Icon } from '../icons-v2-generated/interface/ellipsis-01-icon'
|
|
27
|
+
import { XmarkCircleIcon } from '../icons-v2-generated/signs-and-symbols/xmark-circle-icon'
|
|
28
|
+
import { cn } from '../../utils/cn'
|
|
29
|
+
import { CONTEXT_ICON_CLASS, CONTEXT_LABEL_CLASS, CONTEXT_ROW_CLASS } from './context-items-list'
|
|
30
|
+
import type { ChatContextItem } from './types/context-item.types'
|
|
31
|
+
|
|
32
|
+
const itemKey = (item: { type: string; id: string }) => `${item.type}:${item.id}`
|
|
33
|
+
|
|
34
|
+
export interface ChatContextMemoryBarProps {
|
|
35
|
+
/** Remembered entities, most-recent-first. Empty → the bar renders nothing. */
|
|
36
|
+
items: ChatContextItem[]
|
|
37
|
+
/** Drop one entity from the host's memory (dropdown row `×`). When omitted the
|
|
38
|
+
* list is read-only. */
|
|
39
|
+
onRemove?: (item: ChatContextItem) => void
|
|
40
|
+
/** Lead-glyph resolver — the composer passes its entity-type icon map, so the
|
|
41
|
+
* rows match the picker and the assigned chips. */
|
|
42
|
+
resolveIcon?: (item: ChatContextItem) => React.ReactNode
|
|
43
|
+
/** External disable (e.g. while a message is streaming). */
|
|
44
|
+
disabled?: boolean
|
|
45
|
+
className?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ChatContextMemoryBar({
|
|
49
|
+
items,
|
|
50
|
+
onRemove,
|
|
51
|
+
resolveIcon,
|
|
52
|
+
disabled = false,
|
|
53
|
+
className,
|
|
54
|
+
}: ChatContextMemoryBarProps) {
|
|
55
|
+
const [open, setOpen] = useState(false)
|
|
56
|
+
const rootRef = useRef<HTMLDivElement>(null)
|
|
57
|
+
|
|
58
|
+
// Removing the last remembered entity unmounts the bar; close first so a
|
|
59
|
+
// stale popover can't outlive its trigger.
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (items.length === 0) setOpen(false)
|
|
62
|
+
}, [items.length])
|
|
63
|
+
|
|
64
|
+
// Outside-press close. Registered only while open. The root wraps BOTH the
|
|
65
|
+
// trigger and the popover, so a press on the `⋯` (inside root) is ignored here
|
|
66
|
+
// and handled by the button's own toggle.
|
|
67
|
+
//
|
|
68
|
+
// CAPTURE phase (the `true` 3rd arg): the chat lives inside a resizable drawer
|
|
69
|
+
// whose panel captures pointer events and whose composer sits on a
|
|
70
|
+
// `contentEditable`; a bubble-phase `document` listener can be pre-empted by
|
|
71
|
+
// an ancestor's `stopPropagation`, which is why a press outside the popover
|
|
72
|
+
// wasn't closing it. Capture runs top-down before any of that, so the handler
|
|
73
|
+
// always fires. `touchstart` covers tap-to-dismiss on touch devices.
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!open) return
|
|
76
|
+
const onOutside = (e: Event) => {
|
|
77
|
+
if (rootRef.current?.contains(e.target as Node)) return
|
|
78
|
+
setOpen(false)
|
|
79
|
+
}
|
|
80
|
+
document.addEventListener('mousedown', onOutside, true)
|
|
81
|
+
document.addEventListener('touchstart', onOutside, true)
|
|
82
|
+
return () => {
|
|
83
|
+
document.removeEventListener('mousedown', onOutside, true)
|
|
84
|
+
document.removeEventListener('touchstart', onOutside, true)
|
|
85
|
+
}
|
|
86
|
+
}, [open])
|
|
87
|
+
|
|
88
|
+
if (items.length === 0) return null
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
// `relative` anchors the popover to this strip; `bottom-full` floats it
|
|
92
|
+
// above, matching the context picker's placement over the input.
|
|
93
|
+
<div
|
|
94
|
+
ref={rootRef}
|
|
95
|
+
className={cn(
|
|
96
|
+
'relative flex items-center gap-[var(--spacing-system-xs)] border-b border-ods-border bg-ods-bg p-[var(--spacing-system-xs)]',
|
|
97
|
+
className,
|
|
98
|
+
)}
|
|
99
|
+
onKeyDown={e => {
|
|
100
|
+
if (e.key === 'Escape' && open) {
|
|
101
|
+
e.preventDefault()
|
|
102
|
+
setOpen(false)
|
|
103
|
+
}
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
<p className="min-w-0 flex-1 truncate text-h6 text-ods-text-secondary">
|
|
107
|
+
{items.length} recently viewed {items.length === 1 ? 'item' : 'items'} in context
|
|
108
|
+
</p>
|
|
109
|
+
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
aria-label="Show remembered context"
|
|
113
|
+
aria-expanded={open}
|
|
114
|
+
title="Show remembered context"
|
|
115
|
+
onClick={() => setOpen(o => !o)}
|
|
116
|
+
className={cn(
|
|
117
|
+
'flex size-4 shrink-0 items-center justify-center rounded outline-none transition-colors [&_svg]:size-4',
|
|
118
|
+
open ? 'text-ods-text-primary' : 'text-ods-text-secondary hover:text-ods-text-primary',
|
|
119
|
+
'focus-visible:text-ods-text-primary',
|
|
120
|
+
)}
|
|
121
|
+
>
|
|
122
|
+
<Ellipsis01Icon size={16} />
|
|
123
|
+
</button>
|
|
124
|
+
|
|
125
|
+
{open && (
|
|
126
|
+
<div
|
|
127
|
+
role="list"
|
|
128
|
+
aria-label="Remembered context"
|
|
129
|
+
// Mirrors `ChatContextPicker`'s popover chrome (border / card bg /
|
|
130
|
+
// shadow-md) and its scroll cap, so both dropdowns over the composer
|
|
131
|
+
// read as one component family.
|
|
132
|
+
className="absolute bottom-full right-0 z-50 mb-1 max-h-[min(340px,45vh)] w-[320px] max-w-[90vw] overflow-y-auto overscroll-contain rounded-md border border-ods-border bg-ods-card text-ods-text-primary shadow-md"
|
|
133
|
+
>
|
|
134
|
+
{items.map(item => (
|
|
135
|
+
// Not a `<button>`: the row itself is inert (only the `×` acts), so
|
|
136
|
+
// the remove control isn't nested inside another button.
|
|
137
|
+
<div key={itemKey(item)} role="listitem" className={CONTEXT_ROW_CLASS}>
|
|
138
|
+
{resolveIcon && <span className={CONTEXT_ICON_CLASS}>{resolveIcon(item)}</span>}
|
|
139
|
+
<span className={CONTEXT_LABEL_CLASS} title={item.label}>
|
|
140
|
+
{item.label}
|
|
141
|
+
</span>
|
|
142
|
+
{onRemove && (
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
onClick={() => onRemove(item)}
|
|
146
|
+
disabled={disabled}
|
|
147
|
+
aria-label={`Remove ${item.label} from context`}
|
|
148
|
+
className="flex size-4 shrink-0 items-center justify-center text-ods-text-secondary outline-none transition-colors hover:text-ods-text-primary focus-visible:text-ods-text-primary disabled:cursor-not-allowed disabled:opacity-40 md:size-6"
|
|
149
|
+
>
|
|
150
|
+
<XmarkCircleIcon className="size-4 shrink-0 md:size-6" />
|
|
151
|
+
</button>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
@@ -379,13 +379,21 @@ export interface EmbeddableChatProps {
|
|
|
379
379
|
*/
|
|
380
380
|
onGuidePromptConsumed?: () => void
|
|
381
381
|
/**
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
382
|
+
* CONTEXT MEMORY (Figma 271:38656) — the entities the host collected from the
|
|
383
|
+
* user's navigation history and rides out on every message. Rendered as a
|
|
384
|
+
* summary strip at the top of the composer card ("N recently viewed items in
|
|
385
|
+
* context") with a `⋯` dropdown listing them all, each removable via
|
|
386
|
+
* `onRemove`. Replaces the old under-the-header "current page context" banner.
|
|
387
|
+
*
|
|
388
|
+
* Host-owned data (it lives in the host's navigation store); the lib only
|
|
389
|
+
* places and styles it, resolving lead glyphs from `contextPicker.entityTypes`.
|
|
390
|
+
* Shown only alongside an active `contextPicker` (i.e. Mingo mode); an empty
|
|
391
|
+
* `items` array renders nothing. Keep the object identity stable (`useMemo`).
|
|
387
392
|
*/
|
|
388
|
-
|
|
393
|
+
contextMemory?: {
|
|
394
|
+
items: ChatContextItem[]
|
|
395
|
+
onRemove?: (item: ChatContextItem) => void
|
|
396
|
+
}
|
|
389
397
|
}
|
|
390
398
|
|
|
391
399
|
// =============================================================================
|
|
@@ -918,7 +926,7 @@ function EmbeddableChatInner({
|
|
|
918
926
|
renderContextItem,
|
|
919
927
|
guidePendingPrompt,
|
|
920
928
|
onGuidePromptConsumed,
|
|
921
|
-
|
|
929
|
+
contextMemory,
|
|
922
930
|
}: EmbeddableChatProps) {
|
|
923
931
|
// `shell === 'none'` means the consumer hosts us inside their own panel
|
|
924
932
|
// (e.g. AppLayoutDrawer in openframe-frontend). Several drawer-shell
|
|
@@ -2197,14 +2205,6 @@ function EmbeddableChatInner({
|
|
|
2197
2205
|
<GuideModeBanner className="animate-in fade-in-0 duration-200" />
|
|
2198
2206
|
)}
|
|
2199
2207
|
|
|
2200
|
-
{/* Mingo-mode current-page context banner (Figma 192:51006) —
|
|
2201
|
-
full-bleed row under the header naming the entity whose detail
|
|
2202
|
-
page the user is currently viewing, so they can ask Mingo to
|
|
2203
|
-
recall it. Host-rendered (it reads the host's navigation-context
|
|
2204
|
-
store) and host-gated to "has an open view"; this slot just
|
|
2205
|
-
places it, mirroring `GuideModeBanner` above. */}
|
|
2206
|
-
{activeMode === 'mingo' && mingoContextBanner}
|
|
2207
|
-
|
|
2208
2208
|
{/* Chat-panel row. In the wide Mingo layout (`splitActive`) the
|
|
2209
2209
|
dialog history is hoisted into a fixed 320px "Current Chats"
|
|
2210
2210
|
rail on the left; the stacked layout keeps it inline in the
|
|
@@ -2532,6 +2532,8 @@ function EmbeddableChatInner({
|
|
|
2532
2532
|
selectedContextItems={contextItems}
|
|
2533
2533
|
onToggleContextItem={toggleContextItem}
|
|
2534
2534
|
onRemoveContextItem={removeContextItem}
|
|
2535
|
+
contextMemoryItems={contextMemory?.items}
|
|
2536
|
+
onRemoveContextMemoryItem={contextMemory?.onRemove}
|
|
2535
2537
|
contextPickerOpen={contextPickerOpen}
|
|
2536
2538
|
onOpenContextPicker={openContextPicker}
|
|
2537
2539
|
onCloseContextPicker={closeContextPicker}
|
|
@@ -37,6 +37,7 @@ export * from './chat-header-search-field'
|
|
|
37
37
|
export * from './chat-panel-header'
|
|
38
38
|
export * from './chat-composer'
|
|
39
39
|
export * from './chat-context-picker'
|
|
40
|
+
export * from './chat-context-memory-bar'
|
|
40
41
|
export * from './context-items-list'
|
|
41
42
|
export * from './chat-archive-page'
|
|
42
43
|
export * from './model-display'
|