@admin-layout/ai-chat-ext-browser 12.2.4-alpha.94 → 12.2.5-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/lib/components/MessageWidgets/MessageFlights.d.ts +38 -0
  2. package/lib/components/MessageWidgets/MessageFlights.d.ts.map +1 -0
  3. package/lib/components/MessageWidgets/MessageFlights.js +321 -0
  4. package/lib/components/MessageWidgets/MessageFlights.js.map +1 -0
  5. package/lib/components/MessageWidgets/MessageMedia.d.ts +7 -5
  6. package/lib/components/MessageWidgets/MessageMedia.d.ts.map +1 -1
  7. package/lib/components/MessageWidgets/MessageMedia.js +86 -54
  8. package/lib/components/MessageWidgets/MessageMedia.js.map +1 -1
  9. package/lib/components/MessageWidgets/MessageTravel.d.ts.map +1 -1
  10. package/lib/components/MessageWidgets/MessageTravel.js +9 -1
  11. package/lib/components/MessageWidgets/MessageTravel.js.map +1 -1
  12. package/lib/components/MessageWidgets/index.d.ts +3 -0
  13. package/lib/components/MessageWidgets/index.d.ts.map +1 -1
  14. package/lib/components/MessageWidgets/mediaUtils.d.ts +21 -0
  15. package/lib/components/MessageWidgets/mediaUtils.d.ts.map +1 -0
  16. package/lib/components/MessageWidgets/mediaUtils.js +76 -0
  17. package/lib/components/MessageWidgets/mediaUtils.js.map +1 -0
  18. package/lib/components/index.d.ts +2 -1
  19. package/lib/components/index.d.ts.map +1 -1
  20. package/lib/index.d.ts +3 -0
  21. package/lib/index.d.ts.map +1 -1
  22. package/lib/index.js +1 -1
  23. package/lib/interfaces/ai-chat.interface.d.ts +11 -1
  24. package/lib/interfaces/ai-chat.interface.d.ts.map +1 -1
  25. package/lib/interfaces/index.d.ts +1 -1
  26. package/lib/interfaces/index.d.ts.map +1 -1
  27. package/lib/slot-fill/chat-settings-fill.js +1 -1
  28. package/lib/slot-fill/chat-settings-fill.js.map +1 -1
  29. package/lib/slot-fill/message-ai-assistant-fill.js +1 -1
  30. package/lib/slot-fill/message-ai-assistant-fill.js.map +1 -1
  31. package/lib/slot-fill/message-codeblock-fill.js +1 -1
  32. package/lib/slot-fill/message-codeblock-fill.js.map +1 -1
  33. package/lib/slot-fill/message-connector-fill.js +1 -1
  34. package/lib/slot-fill/message-connector-fill.js.map +1 -1
  35. package/lib/slot-fill/message-generic-fill.js +1 -1
  36. package/lib/slot-fill/message-generic-fill.js.map +1 -1
  37. package/lib/slot-fill/message-media-fill.js +1 -1
  38. package/lib/slot-fill/message-media-fill.js.map +1 -1
  39. package/lib/slot-fill/message-service-fill.js +1 -1
  40. package/lib/slot-fill/message-service-fill.js.map +1 -1
  41. package/lib/slot-fill/message-thinking-fill.js +1 -1
  42. package/lib/slot-fill/message-thinking-fill.js.map +1 -1
  43. package/lib/slot-fill/message-tooluse-fill.js +1 -1
  44. package/lib/slot-fill/message-tooluse-fill.js.map +1 -1
  45. package/lib/slot-fill/message-travel-fill.js +1 -1
  46. package/lib/slot-fill/message-travel-fill.js.map +1 -1
  47. package/lib/slot-fill/message-workflow-fill.js +1 -1
  48. package/lib/slot-fill/message-workflow-fill.js.map +1 -1
  49. package/package.json +7 -5
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { IPost } from 'common';
3
+ import { TravelAttachmentsInterface } from '../../interfaces';
4
+ import { FlightCardView, FlightSort } from '@admin-layout/client';
5
+ export interface IMessageFlightsProps {
6
+ message: IPost;
7
+ attachment: TravelAttachmentsInterface;
8
+ /** Opens the full result set in the travel side panel. */
9
+ onMessageClick?: (ref: IPost) => void;
10
+ }
11
+ export interface IFlightCardProps {
12
+ card: FlightCardView;
13
+ onOpen?: () => void;
14
+ fullWidth?: boolean;
15
+ /** Show the departure date on the card (offers on different dates, or a round trip). */
16
+ showDate?: boolean;
17
+ /** Briefly outlined, e.g. after a quick-pick chip jumped to it. */
18
+ highlighted?: boolean;
19
+ }
20
+ export declare const FlightCard: ({ card, onOpen, fullWidth, showDate, highlighted, }: IFlightCardProps) => React.JSX.Element;
21
+ export interface IFlightCardListProps {
22
+ attachment: TravelAttachmentsInterface;
23
+ /** Fired by a card with no seller link; omit it to render such cards as plain content. */
24
+ onOpen?: () => void;
25
+ /** Ordering; `recommended` (default) keeps the agent's order. */
26
+ sort?: FlightSort;
27
+ }
28
+ /** Every flight as a full-width vertical list, for a side panel or a results page. */
29
+ export declare const FlightCardList: ({ attachment, onOpen, sort }: IFlightCardListProps) => React.JSX.Element;
30
+ /**
31
+ * Flight results as a swipeable row of cards: one card per offer, the next
32
+ * one peeking in so the row reads as scrollable. Above it, the route and date
33
+ * once, and quick picks (cheapest, fastest, best value) that jump to their
34
+ * card. Touch swipes and snaps; a pointer gets arrow buttons, since sideways
35
+ * scrolling is awkward with a mouse.
36
+ */
37
+ export declare const MessageFlights: ({ message, attachment, onMessageClick }: IMessageFlightsProps) => React.JSX.Element;
38
+ //# sourceMappingURL=MessageFlights.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageFlights.d.ts","sourceRoot":"","sources":["../../../src/components/MessageWidgets/MessageFlights.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAKjF,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAEH,cAAc,EAEd,UAAU,EAIb,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,KAAK,CAAC;IACf,UAAU,EAAE,0BAA0B,CAAC;IACvC,0DAA0D;IAC1D,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CACzC;AAqHD,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,UAAU,GAAI,qDAMxB,gBAAgB,sBAoGlB,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACjC,UAAU,EAAE,0BAA0B,CAAC;IACvC,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,iEAAiE;IACjE,IAAI,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,sFAAsF;AACtF,eAAO,MAAM,cAAc,GAAI,8BAA8C,oBAAoB,sBAchG,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,yCAAyC,oBAAoB,sBA8J3F,CAAC"}
@@ -0,0 +1,321 @@
1
+ import React,{useMemo,useRef,useState,useCallback,useEffect}from'react';import {FaPlane}from'@react-icons/all-files/fa/FaPlane.js';import {FiChevronLeft}from'@react-icons/all-files/fi/FiChevronLeft.js';import {FiChevronRight}from'@react-icons/all-files/fi/FiChevronRight.js';import {FiExternalLink}from'@react-icons/all-files/fi/FiExternalLink.js';import {toFlightCards,flightResultSummary,sortFlightCards}from'@admin-layout/client';/** Soft tile colours for the airline monogram, indexed by `airlineTone`. */
2
+ const TONES = ['bg-sky-100 text-sky-700 dark:bg-sky-500/20 dark:text-sky-300', 'bg-violet-100 text-violet-700 dark:bg-violet-500/20 dark:text-violet-300', 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300', 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300', 'bg-rose-100 text-rose-700 dark:bg-rose-500/20 dark:text-rose-300', 'bg-indigo-100 text-indigo-700 dark:bg-indigo-500/20 dark:text-indigo-300', 'bg-teal-100 text-teal-700 dark:bg-teal-500/20 dark:text-teal-300', 'bg-orange-100 text-orange-700 dark:bg-orange-500/20 dark:text-orange-300'];
3
+ const BADGE = {
4
+ cheapest: {
5
+ label: 'Cheapest',
6
+ pill: 'bg-emerald-50 text-emerald-700 ring-emerald-200 dark:bg-emerald-500/15 dark:text-emerald-300 dark:ring-emerald-500/30',
7
+ text: 'text-emerald-600 dark:text-emerald-400'
8
+ },
9
+ fastest: {
10
+ label: 'Fastest',
11
+ pill: 'bg-violet-50 text-violet-700 ring-violet-200 dark:bg-violet-500/15 dark:text-violet-300 dark:ring-violet-500/30',
12
+ text: 'text-violet-600 dark:text-violet-400'
13
+ },
14
+ best: {
15
+ label: 'Best value',
16
+ pill: 'bg-blue-50 text-blue-700 ring-blue-200 dark:bg-blue-500/15 dark:text-blue-300 dark:ring-blue-500/30',
17
+ text: 'text-blue-600 dark:text-blue-400'
18
+ }
19
+ };
20
+ const BadgePill = ({
21
+ badge
22
+ }) => React.createElement("span", {
23
+ className: `inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-[11px] font-semibold leading-4 ring-1 ring-inset ${BADGE[badge].pill}`
24
+ }, BADGE[badge].label);
25
+ /**
26
+ * The route between the two times: duration above, a line with one amber dot
27
+ * per stop and the plane at the arrival end, stops below.
28
+ */
29
+ const RouteLine = ({
30
+ slice
31
+ }) => {
32
+ const stops = slice.stopCount ?? 0;
33
+ return React.createElement("div", {
34
+ className: "flex min-w-0 flex-col items-center self-center px-1",
35
+ "aria-hidden": "true"
36
+ }, React.createElement("p", {
37
+ className: "text-[11px] font-medium leading-4 text-gray-500 dark:text-gray-400"
38
+ }, slice.duration ?? ' '), React.createElement("div", {
39
+ className: "my-1 flex w-full items-center"
40
+ }, React.createElement("span", {
41
+ className: "h-1.5 w-1.5 shrink-0 rounded-full border border-gray-400 bg-white dark:border-gray-500 dark:bg-gray-800"
42
+ }), React.createElement("span", {
43
+ className: "relative h-px flex-1 bg-gray-300 dark:bg-gray-600"
44
+ }, Array.from({
45
+ length: stops
46
+ }, (_, i) => React.createElement("span", {
47
+ key: i,
48
+ className: "absolute top-1/2 h-1.5 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-amber-400 ring-2 ring-white dark:ring-gray-800",
49
+ style: {
50
+ left: `${(i + 1) / (stops + 1) * 100}%`
51
+ }
52
+ }))), React.createElement(FaPlane, {
53
+ className: "ml-0.5 h-3 w-3 shrink-0 text-blue-500 dark:text-blue-400"
54
+ })), React.createElement("p", {
55
+ className: `text-[11px] font-medium leading-4 ${stops === 0 && slice.stops ? 'text-emerald-600 dark:text-emerald-400' : stops > 0 ? 'text-amber-600 dark:text-amber-400' : 'text-gray-400'}`
56
+ }, slice.stops ?? ' '));
57
+ };
58
+ const SliceRow = ({
59
+ slice,
60
+ caption
61
+ }) => React.createElement("div", null, caption ? React.createElement("p", {
62
+ className: "mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500"
63
+ }, caption) : null, React.createElement("div", {
64
+ className: "grid grid-cols-[auto_1fr_auto] items-center gap-2"
65
+ }, React.createElement("div", null, React.createElement("p", {
66
+ className: "text-[22px] font-bold leading-none tracking-tight tabular-nums text-gray-900 dark:text-gray-50"
67
+ }, slice.departTime ?? '--:--'), React.createElement("p", {
68
+ className: "mt-1.5 text-xs font-medium text-gray-500 dark:text-gray-400"
69
+ }, slice.origin)), React.createElement(RouteLine, {
70
+ slice: slice
71
+ }), React.createElement("div", {
72
+ className: "text-right"
73
+ }, React.createElement("p", {
74
+ className: "text-[22px] font-bold leading-none tracking-tight tabular-nums text-gray-900 dark:text-gray-50"
75
+ }, slice.arriveTime ?? '--:--', slice.dayOffset ? React.createElement("sup", {
76
+ className: "ml-0.5 text-[11px] font-semibold text-amber-600 dark:text-amber-400"
77
+ }, slice.dayOffset) : null), React.createElement("p", {
78
+ className: "mt-1.5 text-xs font-medium text-gray-500 dark:text-gray-400"
79
+ }, slice.destination))));
80
+ const CARD_CLASS = 'group/card relative flex flex-col rounded-2xl border bg-white p-4 text-left shadow-sm transition-all duration-200 ' + 'hover:-translate-y-0.5 hover:border-blue-300 hover:shadow-lg hover:shadow-blue-500/10 ' + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 ' + 'dark:bg-gray-800 dark:hover:border-blue-500/60 dark:hover:shadow-black/30 dark:focus-visible:ring-offset-gray-900';
81
+ const IN_ROW = 'w-[min(20rem,85%)] shrink-0 snap-start';
82
+ const FlightCard = ({
83
+ card,
84
+ onOpen,
85
+ fullWidth = false,
86
+ showDate = false,
87
+ highlighted = false
88
+ }) => {
89
+ const className = `${CARD_CLASS} ${fullWidth ? 'w-full' : IN_ROW} ${highlighted ? 'border-blue-400 ring-2 ring-blue-500/40 dark:border-blue-400' : 'border-gray-200 dark:border-gray-700'}`;
90
+ const subtitle = [card.flightNumber, card.cabin].filter(Boolean).join(' · ') || (card.roundTrip ? 'Round trip' : 'One way');
91
+ const captionFor = (s, i) => {
92
+ if (card.roundTrip) return [i === 0 ? 'Outbound' : 'Return', s.date].filter(Boolean).join(' · ');
93
+ return showDate ? s.date : undefined;
94
+ };
95
+ const body = React.createElement(React.Fragment, null, React.createElement("div", {
96
+ className: "flex items-center gap-2.5"
97
+ }, React.createElement("span", {
98
+ "aria-hidden": "true",
99
+ className: `flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-[11px] font-bold tracking-wide ${TONES[card.airlineTone % TONES.length]}`
100
+ }, card.airlineCode), React.createElement("div", {
101
+ className: "min-w-0 flex-1"
102
+ }, React.createElement("p", {
103
+ className: "truncate text-sm font-semibold leading-5 text-gray-900 dark:text-gray-50"
104
+ }, card.airline ?? card.footer), React.createElement("p", {
105
+ className: "truncate text-xs leading-4 text-gray-500 dark:text-gray-400"
106
+ }, subtitle)), card.badges[0] ? React.createElement(BadgePill, {
107
+ badge: card.badges[0]
108
+ }) : null), React.createElement("div", {
109
+ className: "my-4 space-y-4"
110
+ }, card.slices.map((s, i) => React.createElement(SliceRow, {
111
+ key: i,
112
+ slice: s,
113
+ caption: captionFor(s, i)
114
+ }))), React.createElement("div", {
115
+ className: "mt-auto flex items-center justify-between gap-3 border-t border-gray-100 pt-3 dark:border-gray-700"
116
+ }, React.createElement("p", {
117
+ className: "min-w-0 truncate text-xs text-gray-500 dark:text-gray-400"
118
+ }, card.seller ? `via ${card.seller}` : card.roundTrip ? 'Round trip' : card.slices[0].stops ?? ''), React.createElement("span", {
119
+ className: "flex shrink-0 items-center gap-1.5"
120
+ }, card.price ? React.createElement("span", {
121
+ className: "text-lg font-bold leading-none tabular-nums text-gray-900 dark:text-white"
122
+ }, card.price) : null, card.url ? React.createElement(FiExternalLink, {
123
+ className: "h-3.5 w-3.5 text-gray-400 transition-colors group-hover/card:text-blue-500"
124
+ }) : null)));
125
+ // A bookable offer goes straight to the seller; without a link the card
126
+ // opens the full results in the side panel instead.
127
+ if (card.url) {
128
+ return React.createElement("a", {
129
+ href: card.url,
130
+ target: "_blank",
131
+ rel: "noopener noreferrer",
132
+ "aria-label": card.accessibleLabel,
133
+ className: className,
134
+ "data-flight-card": "",
135
+ "data-flight-key": card.key
136
+ }, body);
137
+ }
138
+ if (!onOpen) {
139
+ // Nothing to open: plain content, not a button that does nothing.
140
+ return React.createElement("div", {
141
+ role: "group",
142
+ "aria-label": card.accessibleLabel,
143
+ className: className,
144
+ "data-flight-card": "",
145
+ "data-flight-key": card.key
146
+ }, body);
147
+ }
148
+ return React.createElement("button", {
149
+ type: "button",
150
+ onClick: onOpen,
151
+ "aria-label": card.accessibleLabel,
152
+ className: className,
153
+ "data-flight-card": "",
154
+ "data-flight-key": card.key
155
+ }, body);
156
+ };
157
+ /** Every flight as a full-width vertical list, for a side panel or a results page. */
158
+ const FlightCardList = ({
159
+ attachment,
160
+ onOpen,
161
+ sort = 'recommended'
162
+ }) => {
163
+ const cards = useMemo(() => toFlightCards(attachment), [attachment]);
164
+ const summary = useMemo(() => flightResultSummary(attachment, cards), [attachment, cards]);
165
+ const ordered = useMemo(() => sortFlightCards(cards, sort), [cards, sort]);
166
+ if (cards.length === 0) return null;
167
+ return React.createElement("ul", {
168
+ className: "space-y-3",
169
+ "aria-label": attachment.title || 'Flight options'
170
+ }, ordered.map(c => React.createElement("li", {
171
+ key: c.key
172
+ }, React.createElement(FlightCard, {
173
+ card: c,
174
+ onOpen: onOpen,
175
+ fullWidth: true,
176
+ showDate: !summary.sameDate
177
+ }))));
178
+ };
179
+ const HIGHLIGHT_MS = 1600;
180
+ /**
181
+ * Flight results as a swipeable row of cards: one card per offer, the next
182
+ * one peeking in so the row reads as scrollable. Above it, the route and date
183
+ * once, and quick picks (cheapest, fastest, best value) that jump to their
184
+ * card. Touch swipes and snaps; a pointer gets arrow buttons, since sideways
185
+ * scrolling is awkward with a mouse.
186
+ */
187
+ const MessageFlights = ({
188
+ message,
189
+ attachment,
190
+ onMessageClick
191
+ }) => {
192
+ const cards = useMemo(() => toFlightCards(attachment), [attachment]);
193
+ const summary = useMemo(() => flightResultSummary(attachment, cards), [attachment, cards]);
194
+ const rowRef = useRef(null);
195
+ const [edges, setEdges] = useState({
196
+ atStart: true,
197
+ atEnd: true
198
+ });
199
+ const [highlighted, setHighlighted] = useState(null);
200
+ const measure = useCallback(() => {
201
+ const el = rowRef.current;
202
+ if (!el) return;
203
+ setEdges({
204
+ atStart: el.scrollLeft <= 1,
205
+ atEnd: el.scrollLeft + el.clientWidth >= el.scrollWidth - 1
206
+ });
207
+ }, []);
208
+ useEffect(() => {
209
+ measure();
210
+ const el = rowRef.current;
211
+ if (!el || typeof ResizeObserver === 'undefined') return undefined;
212
+ const ro = new ResizeObserver(measure);
213
+ ro.observe(el);
214
+ return () => ro.disconnect();
215
+ }, [measure, cards.length]);
216
+ useEffect(() => {
217
+ if (!highlighted) return undefined;
218
+ const t = setTimeout(() => setHighlighted(null), HIGHLIGHT_MS);
219
+ return () => clearTimeout(t);
220
+ }, [highlighted]);
221
+ if (cards.length === 0) return null;
222
+ const step = dir => {
223
+ const el = rowRef.current;
224
+ const card = el?.querySelector('[data-flight-card]');
225
+ if (!el || !card) return;
226
+ el.scrollBy({
227
+ left: dir * (card.offsetWidth + 12),
228
+ behavior: 'smooth'
229
+ });
230
+ };
231
+ const jumpTo = key => {
232
+ const el = rowRef.current;
233
+ const card = el?.querySelector(`[data-flight-key="${CSS.escape(key)}"]`);
234
+ if (!el || !card) return;
235
+ // 8px: the row's px-2, so the card lands where the first one sits.
236
+ el.scrollTo({
237
+ left: card.offsetLeft - 8,
238
+ behavior: 'smooth'
239
+ });
240
+ setHighlighted(key);
241
+ };
242
+ const open = () => onMessageClick?.(message);
243
+ const count = summary.count;
244
+ const title = summary.route ?? attachment.title ?? 'Flights';
245
+ const subtitle = [summary.date, `${count} ${count === 1 ? 'flight' : 'flights'}`, summary.fromPrice ? `from ${summary.fromPrice}` : undefined, summary.seller].filter(Boolean).join(' · ');
246
+ const picks = cards.length > 2 ? summary.picks : [];
247
+ // Arrows sit over the first and last visible card, so they show on hover or
248
+ // keyboard focus only; the next card peeking in already says the row scrolls.
249
+ const arrow = 'absolute top-1/2 z-10 hidden h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full border border-gray-200 bg-white/95 text-gray-700 shadow-md transition hover:bg-white ' + 'opacity-0 group-hover/row:opacity-100 focus-visible:opacity-100 disabled:pointer-events-none disabled:!opacity-0 [@media(pointer:fine)]:flex ' + 'dark:border-gray-600 dark:bg-gray-800/95 dark:text-gray-200';
250
+ return React.createElement("div", {
251
+ className: "my-2 w-full",
252
+ role: "group",
253
+ "aria-label": attachment.title || 'Flight options'
254
+ }, React.createElement("div", {
255
+ className: "mx-2 mb-2 flex items-center justify-between gap-3"
256
+ }, React.createElement("div", {
257
+ className: "flex min-w-0 items-center gap-2.5"
258
+ }, React.createElement("span", {
259
+ "aria-hidden": "true",
260
+ className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-blue-50 text-blue-600 dark:bg-blue-500/15 dark:text-blue-300"
261
+ }, React.createElement(FaPlane, {
262
+ className: "h-4 w-4"
263
+ })), React.createElement("div", {
264
+ className: "min-w-0"
265
+ }, React.createElement("p", {
266
+ className: "truncate text-sm font-semibold leading-5 text-gray-900 dark:text-gray-50"
267
+ }, title), React.createElement("p", {
268
+ className: "truncate text-xs leading-4 text-gray-500 dark:text-gray-400"
269
+ }, subtitle))), onMessageClick && count > 1 ? React.createElement("button", {
270
+ type: "button",
271
+ onClick: open,
272
+ className: "inline-flex shrink-0 items-center gap-0.5 rounded-full px-2.5 py-1 text-xs font-semibold text-blue-600 transition hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-500/10"
273
+ }, "View all ", count, React.createElement(FiChevronRight, {
274
+ className: "h-3.5 w-3.5"
275
+ })) : null), picks.length > 0 ? React.createElement("div", {
276
+ className: "mx-2 mb-2 flex flex-wrap gap-1.5",
277
+ "aria-label": "Quick picks"
278
+ }, picks.map(p => React.createElement("button", {
279
+ key: p.badge,
280
+ type: "button",
281
+ onClick: () => jumpTo(p.key),
282
+ className: "inline-flex items-center gap-1.5 rounded-full border border-gray-200 bg-white px-2.5 py-1 text-xs transition hover:border-blue-300 hover:bg-blue-50/60 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-blue-500/60 dark:hover:bg-blue-500/10"
283
+ }, React.createElement("span", {
284
+ className: `font-semibold ${BADGE[p.badge].text}`
285
+ }, p.label), React.createElement("span", {
286
+ className: "font-semibold tabular-nums text-gray-900 dark:text-gray-50"
287
+ }, p.badge === 'fastest' ? p.duration ?? p.price : p.price), p.airline ? React.createElement("span", {
288
+ className: "truncate text-gray-500 dark:text-gray-400"
289
+ }, "\u00B7 ", p.airline) : null))) : null, React.createElement("div", {
290
+ className: "group/row relative"
291
+ }, React.createElement("div", {
292
+ ref: rowRef,
293
+ onScroll: measure,
294
+ // scroll-px-2 matches px-2: without it a snapped card sits flush
295
+ // against the edge while the first sits 8px in, and snapping
296
+ // nudges the row off 0 on load so "previous" looks enabled.
297
+ className: "flex snap-x snap-mandatory scroll-px-2 gap-3 overflow-x-auto scroll-smooth px-2 pb-2 pt-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
298
+ }, cards.map(c => React.createElement(FlightCard, {
299
+ key: c.key,
300
+ card: c,
301
+ onOpen: open,
302
+ showDate: !summary.sameDate,
303
+ highlighted: highlighted === c.key
304
+ }))), React.createElement("button", {
305
+ type: "button",
306
+ "aria-label": "Previous flights",
307
+ onClick: () => step(-1),
308
+ disabled: edges.atStart,
309
+ className: `${arrow} left-0`
310
+ }, React.createElement(FiChevronLeft, {
311
+ className: "h-5 w-5"
312
+ })), React.createElement("button", {
313
+ type: "button",
314
+ "aria-label": "More flights",
315
+ onClick: () => step(1),
316
+ disabled: edges.atEnd,
317
+ className: `${arrow} right-0`
318
+ }, React.createElement(FiChevronRight, {
319
+ className: "h-5 w-5"
320
+ }))));
321
+ };export{FlightCard,FlightCardList,MessageFlights};//# sourceMappingURL=MessageFlights.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageFlights.js","sources":["../../../src/components/MessageWidgets/MessageFlights.tsx"],"sourcesContent":[null],"names":[],"mappings":"ibAwBA;AACA,MAAM,KAAK,GAAG,CAAA,8DAAA,EAAA,0EAAA,EAAA,8EAAA,EAAA,sEAAA,EAAA,kEAAA,EAAA,0EAAA,EAAA,kEAAA,EAAA,0EAAA,CAAA;MACV,KAAA,GAAA;UACA,EAAA;IACA,KAAA,EAAA,UAAA;IACA,IAAA,EAAA,uHAAsE;IACtE,IAAA,EAAA;;SAEA,EAAA;IACA,KAAA,EAAA,SAAA;IACF,IAAA,EAAA,iHAAA;AAEF,IAAA;AACI,GAAA;AACI,EAAA,IAAA,EAAA;AACA,IAAA,KAAA,EAAA,YAAM;AACN,IAAA,IAAA,EAAA,qGAA8C;AACjD,IAAA,IAAA,EAAA;AACD;AACI,CAAA;AACA,MAAA,SAAM,GAAA,CAAA;AACN,EAAA;AACH,CAAA,KAAA,KAAA,CAAA,aAAA,CAAA,MAAA,EAAA;AACD,EAAA,SAAM,EAAA,CAAA,iHAAA,EAAA,KAAA,CAAA,KAAA,CAAA,CAAA,IAAA,CAAA;AACF,CAAA,EAAA,KAAA,CAAA,MAAO,CAAA,KAAA,CAAA;AACP;AACA;AACH;;AAGL,MAAM,SAAS,GAAG,CAAC;AAQnB,EAAA;;;AAGG,EAAA,OAAA,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA;AACH,IAAA,WAAe,qDAA6C;AACxD,IAAA,aAAW,EAAG;AACd,GAAA,EAAA,KAAA,CAAO,aACH,CAAA,GAAA,EAAA;aACI,EAAA;UAGA,CAAA,QAAA,IAAA,GAAA,CAAA,EAAA,KAAA,CAAA,aAAe,CAAA,KAAA,EAAA;aACX,EAAA;wBACA,CAAA,MAAA,EAAA;AASA,IAAA,SAAA,EAAA;WAEJ,CAAA,aAAA,CAAA,MAAA,EAAA;AAGY,IAAA,SAAA,EAAA;gBACA;AACE,IAAA,MAAA,EAAA;AACA,GAAA,EAAA,CAAA,CAAA,EAAA,CAAA,KAAA,KAAA,CAAC,aAAC,CAAA,MACV,EAED;AAIjB,IAAE,GAAA,EAAA,CAAA;AAEF,IAAA,SAAM,EAAQ,+HACV;AACK,IAAA,KAAA,EAAO;MAKR,IAAA,EAAA,CAAA,EAAA,CAAA,CAAA,GAAA,CAAA,KAAA,KAAA,GAAK,CAAA,CAAA,GAAA,GAAS,EAAC;AACX;aACI,CAAA,aAAA,CAAA,OAAA,EAAA;aAGA,EAAA;AAEJ,GAAA,CAAA,CAAA,EAAA,KAAA,CAAA,aAAA,CAAA,GAAC,EAAA;aACD,EAAA,CAAA,kCAAe,EAAA,KAAA,KAAY,CAAA,IAAA,KAAA,CAAA,KAAA,GAAA,wCAAA,GAAA,KAAA,GAAA,CAAA,GAAA,oCAAA,GAAA,eAAA,CAAA;gBACvB,IAAA,GAAA,CAAA,CAAA;;iBAEK,CAAA;AAML,EAAA,KAAA;AAMhB,EAAA;MAEI,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA,IAAA,EAAA,OAAA,GAAA,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA;WACA,EAAA;AACA,CAAA,EAAA,OAAA,CAAA,GAAA,IAAA,EAAA,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA;AAEJ,EAAA,SAAM,EAAM;AAYZ,CAAA,EAAA,mBAAa,CAAU,KAAI,EACvB,IAAI,EACJ,KAAM,CAAA,aACM,CAAA,GAAA,EAAK;AAIjB,EAAA,SAAM,EAAA;AAEE,CAAA,EAAA,KAAA,CAAC,UAAC,IAAA,OAAA,CAAA,EAAA,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA;WACA,EAAA;AAEV,CAAA,EAAA,KAAA,CAAA,MAAM,CAAA,CAAA,EAAQ,KACT,CAAA,aAAK,CAAY;AACtB,EAAA,KAAA,EAAA;SACI,CAAA,aAAS,CAAA,KAAS,EAAA;aAAE;QACpB,CAAA,aAAO,CAAQ,GAAG,EAAE;AACxB,EAAA,SAAE,EAAA;QAEF,CAAA,UACI,IAAA,OAAA,EAAA,KAAA,CAAA,SAAA,GAAA,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA;WACI,EAAA;kBACI,CAAA,GAAA,IAAA,CAAA,EAAA,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA;aAQA;oBACI,CAAA,CAAA,CAAA,CAAA;AAGA,MAAA,UAAA,GAAA,oHACE,GAAA,wFAAA,GAAA,0GAAA,GAAA,mHAAA;YACL,GAAA,wCAAkB;AAEvB,MAAA,UAAA,GAAA,CAAA;;AAQI,EAAA,MAAA;cAGA,KAAA;AACK,EAAA,QAAA,GAAA,KAAA;gBAKA;MAQjB;QACA,SAAA,GAAA,CAAA,EAAA,UAAA,CAAA,CAAA,EAAA,SAAA,GAAA,QAAoD,GAAA,MAAA,CAAA,CAAA,EAAA,WAAA,GAAA,8DAAA,GAAA,sCAAA,CAAA,CAAA;AACpD,EAAA,MAAI,QAAQ,GAAG,CAAA,IAAA,CAAA,YAAA,EAAA,IAAA,CAAA,KAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA,CAAA,IAAA,CAAA,KAAA,CAAA,KAAA,IAAA,CAAA,SAAA,GAAA,YAAA,GAAA,SAAA,CAAA;AACX,EAAA,MAAA,UACI,GAAA,CAAA,CAAA,EAAA,CAAA,KAAA;IAYR,IAAC,IAAA,CAAA,SAAA,EAAA,OAAA,CAAA,CAAA,KAAA,CAAA,GAAA,UAAA,GAAA,QAAA,EAAA,CAAA,CAAA,IAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA,CAAA,IAAA,CAAA,KAAA,CAAA;IACD,OAAK,QAAS,GAAA,CAAA,CAAA,IAAA,GAAA,SAAA;;QAEV,IAAA,GAAO,kCAEK,EAAC,WAAO,CAAA,aACA,CAAA,KAAK,EAAA;IAQ7B,SAAC,EAAA;KACD,KAAA,CAAO,aACH,CAAA,MAAA,EAAA;AAWR,IAAE,aAAA,EAAA,MAAA;AAUF,IAAA,SAAA,EAAA,CAAA,iGAAsF,EAAA,KAAA,CAAA,IAAA,CAAA,WAAA,GAAA,KAAA,CAAA,MAAA,CAAA,CAAA;AACtF,GAAA,EAAA,IAAO,CAAA,WAAM,CAAA,EAAA,KAAc,CAAG,aAAa,CAAA,KAAE,EAAM;AAC/C,IAAA,WAAW;KACX,KAAM,CAAA,aAAU,CAAA,GAAQ;IACxB,SAAM,EAAA;AACN,GAAA,EAAA,IAAI,CAAA,OAAM,IAAM,IAAK,CAAC,MAAA,CAAA,EAAA,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA;AAAE,IAAA,SAAA,EAAO;KAC/B,QACI,CAAA,CAAA,EAAA,IAAA,CAAA,MAAA,CAAA,CAAA,CAAA,GAAA,KAAA,CAAI,aAAU,CAAA,SAAW,EAAA;SAGb,EAAA,IAAA,CAAA,MAAA,CAAA,CAAA;AAKpB,GAAE,CAAA,GAAA,IAAA,CAAA,EAAA,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA;AAEF,IAAA,SAAM,EAAA;AAEN,GAAA,EAAA,IAAA,CAAA,MAAA,CAAA,GAAA,CAAA,CAAA,CAAA,EAAA,CAAA,KAAA,KAAA,CAAA,aAAA,CAAA,QAAA,EAAA;;;;;;AAMG,GAAA,EAAA,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA;AACH,IAAA,SAAa,EAAA;AACT,GAAA,EAAA,IAAA,CAAM,MAAK,GAAG,CAAA,IAAA,EAAQ,KAAK,MAAC,CAAA,CAAA,GAAA,IAAc,CAAA,SAAU,GAAG,YAAY,GAAE,IAAA,CAAA,MAAA,CAAA,CAAA,CAAA,CAAA,KAAA,IAAA,EAAA,CAAA,EAAA,KAAA,CAAA,aAAA,CAAA,MAAA,EAAA;IACrE,SAAM,EAAA;AACN,GAAA,EAAA,IAAA,CAAM,QAAM,KAAG,CAAA,aAA6B,CAAA,MAAA,EAAA;AAC5C,IAAA,SAAO,EAAA;KACP,IAAA,CAAM,KAAC,CAAA,GAAA,IAAW,EAAE,IAAA,CAAA,GAAA,GAAA,MAAkB,aAA4B,CAAC,cAAC,EAAA;AAEpE,IAAA,SAAM,EAAA;AACF,GAAA,CAAA,GAAA,IAAA,CAAA,CAAA,CAAM;AACN;;AACA,EAAA,IAAA,IAAA,CAAA,GAAA,EAAQ;AACJ,IAAA,OAAA,KAAA,CAAA,aAAY,CAAA,GAAU;AACtB,MAAA,IAAA,EAAA,IAAA,CAAK,GAAE;AACV,MAAA,MAAE,EAAA,QAAA;MACN,GAAI,EAAE,qBAAA;MAEP,YAAa,EAAE,IAAA,CAAA,eAAA;AACX,MAAA,SAAO,EAAE,SAAC;AACV,MAAA,kBAAW,EAAO;AAClB,MAAA,iBAAW,EAAA,IAAO,CAAA;AAAgC,KAAA,EAAA,IAAA,CAAA;AAClD,EAAA;AACA,EAAA,IAAA,CAAA,MAAG,EAAA;AACH;IACJ,OAAI,KAAO,CAAE,aAAa,CAAC,KAAC,EAAA;MAE5B,IAAA,EAAA,OAAe;AACX,MAAA,YAAK,EAAA,IAAW,CAAA,eAAA;AAAE,MAAA,SAAA,EAAA;AAClB,MAAA,kBAAU,EAAA,EAAU;AACpB,MAAA;AACJ,KAAC,EAAE,IAAC,CAAA;AAEJ,EAAA;AAAwB,EAAA,OAAA,KAAA,CAAO,aAAK,CAAA,QAAA,EAAA;AAEpC,IAAA,IAAA,EAAM,QAAQ;AACV,IAAA,OAAA,EAAA,MAAW;gBACL,EAAI,IAAG,CAAE,eAAe;AAC9B,IAAA,SAAK,EAAE,SAAS;sBAAS,EAAA,EAAA;qBACb,MAAM,CAAE;AACxB,GAAA,EAAC,IAAC,CAAA;AAEF;AACI;AACA,MAAA;AACA,EAAA,UAAI;;SACJ;AACA,CAAA,KAAA;QACA,KAAA,GAAA,OAAe,CAAA,MAAK,aAAA,CAAA,UAAA,CAAA,EAAA,CAAA,UAAA,CAAA,CAAA;AACxB,EAAA,MAAE,OAAA,GAAA,OAAA,CAAA,MAAA,mBAAA,CAAA,UAAA,EAAA,KAAA,CAAA,EAAA,CAAA,UAAA,EAAA,KAAA,CAAA,CAAA;QAEF,OAAU,GAAG,6BAA8B,CAAC,KAAC,EAAA,IAAA,CAAA,EAAA,CAAA,KAAA,EAAA,IAAA,CAAA,CAAA;AAC7C,EAAA,IAAA,KAAM,CAAA,MAAQ,KAAA,CAAA,EAAQ,OAAM,IAAA;SAC5B,KAAM,CAAK,aAAW,CAAA,IAAK,EAAI;AAC/B,IAAA,sBAAiB;AACb,IAAA,YAAQ,EAAA,UAAI,CAAA,KAAA,IAAA;AACZ,GAAA,EAAA,OAAG,CAAA,GAAK,CAAA,CAAA,IAAI,KAAK,CAAA,aAAS,CAAQ;AAClC,IAAA,GAAA,EAAA,CAAA,CAAA;AACA,GAAA,EAAA,KAAA,CAAA,aAAc,CAAA,UAAA,EAAA;AACjB,IAAA,IAAA,EAAA,CAAA;UACI,EAAA;aACI,EAAC,IAAK;AACf,IAAA,WAAW,OAAG,CAAK;IACnB,CAAA,CAAA,CAAA;;MAEA,YACI,GAAA,IAAA;;AAEA;AAEJ;;;AAIgB;AAII;oBAEJ,GAAA,CAAA;AACI,EAAA,OAAA;AAGA,EAAA,UAAA;AAGP,EAAA;;gBAMiB,OAAK,CAAA,MAAA,aAAA,CAAA,UAAA,CAAA,EAAA,CAAA,UAAA,CAAA,CAAA;AACf,EAAA,MAAA,OAAA,GAAA,OAAA,CAAA,MAAA,mBAAe,CAAA,UAAC,EAAS,KAAC,CAAA,EAAA,CAAA,UAEjC,EAAE,KAAK,CACN,CAAA;QAEL,MAAM,SAAS,CAAC,IACb,CAAA;AAQY,EAAA,MAAA,CAAA,KAAA,EAAA,QAAA,CAAA,GAAA,QAAA,CAAA;aACA,IAAA;;;AAImE,EAAA,MAAA,CAAA,WAAE,EAAA,cAG5E,CAAC,GAER,QAAM,CAAA,IAAA,CAAA;QAER,OAAA,GAAA,WAAA,CAAA,MAAA;AACI,IAAA,MAAA,EAAA,GAAA,MAAA,CAAA,OAAA;;;iBAKI,CAAA,UAAA,IAAA,CAAA;gBACA,UAAS;;AAmBT,EAAA,CAAA,EAAA,EAAA,CAAA;YAEJ,MAAA;;AAYhB,IAAE,MAAA,EAAA,GAAA,MAAA,CAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -7,11 +7,13 @@ export interface IMessageMediaProps {
7
7
  onMessageClick?: (ref: IPost) => void;
8
8
  }
9
9
  /**
10
- * Media card — renders the image / video / audio a cdecli generation connector
11
- * (yantra-image-generation, yantra-video-generation, audio / TTS, …) returned,
12
- * inline in the chat. Shows the asset(s), the prompt as a caption, an
13
- * attribution line (connector · model), and open / download actions. The asset
14
- * URLs live on the message's propsConfiguration, so they persist with the chat.
10
+ * Media result card — renders the image / video / audio a cdecli generation
11
+ * connector (yantra-image-generation, yantra-video-generation, audio / TTS, …)
12
+ * returned, inline in the chat. Shows the asset(s) in a real player, a technical
13
+ * metadata row (kind · dimensions · duration · format · model) derived from the
14
+ * loaded asset, the prompt as a caption, and open / download / copy-link actions.
15
+ * The asset URLs live on the message's propsConfiguration, so they persist with
16
+ * the chat.
15
17
  */
16
18
  export declare const MessageMedia: (props: IMessageMediaProps) => React.JSX.Element;
17
19
  //# sourceMappingURL=MessageMedia.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageMedia.d.ts","sourceRoot":"","sources":["../../../src/components/MessageWidgets/MessageMedia.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAI/B,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CACzC;AAQD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,kBAAkB,sBAsFrD,CAAC"}
1
+ {"version":3,"file":"MessageMedia.d.ts","sourceRoot":"","sources":["../../../src/components/MessageWidgets/MessageMedia.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAQxC,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAa/B,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CACzC;AAaD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,kBAAkB,sBAyIrD,CAAC"}
@@ -1,29 +1,28 @@
1
- import React from'react';import {FiImage}from'@react-icons/all-files/fi/FiImage.js';import {FiVideo}from'@react-icons/all-files/fi/FiVideo.js';import {FiMusic}from'@react-icons/all-files/fi/FiMusic.js';import {FiDownload}from'@react-icons/all-files/fi/FiDownload.js';import {FiExternalLink}from'@react-icons/all-files/fi/FiExternalLink.js';import {MediaAsset}from'@admin-layout/connector-widgets-browser';const KIND_META = {
2
- image: {
3
- icon: React.createElement(FiImage, {
4
- className: "w-4 h-4"
5
- }),
6
- label: 'Image'
7
- },
8
- video: {
9
- icon: React.createElement(FiVideo, {
10
- className: "w-4 h-4"
11
- }),
12
- label: 'Video'
13
- },
14
- audio: {
15
- icon: React.createElement(FiMusic, {
16
- className: "w-4 h-4"
17
- }),
18
- label: 'Audio'
19
- }
1
+ import React,{useState}from'react';import {FiImage}from'@react-icons/all-files/fi/FiImage.js';import {FiVideo}from'@react-icons/all-files/fi/FiVideo.js';import {FiMusic}from'@react-icons/all-files/fi/FiMusic.js';import {FiDownload}from'@react-icons/all-files/fi/FiDownload.js';import {FiExternalLink}from'@react-icons/all-files/fi/FiExternalLink.js';import {FiCopy}from'@react-icons/all-files/fi/FiCopy.js';import {FiCheck}from'@react-icons/all-files/fi/FiCheck.js';import {MediaAsset}from'@admin-layout/connector-widgets-browser';import {deriveMediaItems,MEDIA_KIND_LABEL,formatAttribution,formatDimensions,formatDuration,formatMediaFormat}from'./mediaUtils.js';const KIND_ICON = {
2
+ image: React.createElement(FiImage, {
3
+ className: "w-3.5 h-3.5"
4
+ }),
5
+ video: React.createElement(FiVideo, {
6
+ className: "w-3.5 h-3.5"
7
+ }),
8
+ audio: React.createElement(FiMusic, {
9
+ className: "w-3.5 h-3.5"
10
+ })
20
11
  };
12
+ /** One compact `·`-separated metadata cell. */
13
+ const MetaCell = ({
14
+ children
15
+ }) => React.createElement("span", {
16
+ className: "inline-flex items-center whitespace-nowrap"
17
+ }, children);
21
18
  /**
22
- * Media card — renders the image / video / audio a cdecli generation connector
23
- * (yantra-image-generation, yantra-video-generation, audio / TTS, …) returned,
24
- * inline in the chat. Shows the asset(s), the prompt as a caption, an
25
- * attribution line (connector · model), and open / download actions. The asset
26
- * URLs live on the message's propsConfiguration, so they persist with the chat.
19
+ * Media result card — renders the image / video / audio a cdecli generation
20
+ * connector (yantra-image-generation, yantra-video-generation, audio / TTS, …)
21
+ * returned, inline in the chat. Shows the asset(s) in a real player, a technical
22
+ * metadata row (kind · dimensions · duration · format · model) derived from the
23
+ * loaded asset, the prompt as a caption, and open / download / copy-link actions.
24
+ * The asset URLs live on the message's propsConfiguration, so they persist with
25
+ * the chat.
27
26
  */
28
27
  const MessageMedia = props => {
29
28
  const {
@@ -34,58 +33,91 @@ const MessageMedia = props => {
34
33
  const {
35
34
  attachment
36
35
  } = message.propsConfiguration?.contents ?? {};
36
+ const [assetMeta, setAssetMeta] = useState({});
37
+ const [copied, setCopied] = useState(false);
37
38
  if (!attachment || !attachment.url) return null;
38
- const items = attachment.items && attachment.items.length > 0 ? attachment.items : [{
39
- kind: attachment.kind,
40
- url: attachment.url,
41
- title: attachment.title,
42
- mimeType: attachment.mimeType,
43
- posterUrl: attachment.posterUrl
44
- }];
45
- const meta = KIND_META[attachment.kind] ?? KIND_META.image;
46
- const title = attachment.title || `${meta.label} generated`;
47
- const attribution = [attachment.connector, attachment.model].filter(Boolean).join(' · ');
48
- const isGrid = items.length > 1 && attachment.kind === 'image';
39
+ const items = deriveMediaItems(attachment);
40
+ if (items.length === 0) return null;
41
+ const kind = attachment.kind;
42
+ const kindLabel = MEDIA_KIND_LABEL[kind] ?? MEDIA_KIND_LABEL.image;
43
+ const kindIcon = KIND_ICON[kind] ?? KIND_ICON.image;
44
+ const title = attachment.title || `${kindLabel} generated`;
45
+ const attribution = formatAttribution(attachment);
46
+ const isImageGrid = items.length > 1 && kind === 'image';
47
+ // Technical metadata — dimensions/duration come from the loaded asset (single
48
+ // asset only; a grid has no one set of dimensions), format from the mime type.
49
+ const dimensions = !isImageGrid ? formatDimensions(assetMeta.width, assetMeta.height) : undefined;
50
+ const duration = !isImageGrid ? formatDuration(assetMeta.duration) : undefined;
51
+ const format = formatMediaFormat(attachment.mimeType, attachment.fileName);
52
+ const metaCells = [isImageGrid ? `${items.length} images` : undefined, dimensions, duration, format, attribution].filter(Boolean);
49
53
  const open = () => onMessageClick?.(message);
54
+ const copyLink = async () => {
55
+ try {
56
+ await navigator.clipboard?.writeText(attachment.url);
57
+ setCopied(true);
58
+ window.setTimeout(() => setCopied(false), 1600);
59
+ } catch {
60
+ /* clipboard unavailable — no-op */
61
+ }
62
+ };
50
63
  return React.createElement("div", {
51
64
  key: `${index}`,
52
- className: "my-2 mx-2 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800/60 overflow-hidden"
65
+ className: "my-2 mx-2 w-[calc(100%-1rem)] rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800/60 overflow-hidden"
53
66
  }, React.createElement("div", {
54
- className: "flex items-center gap-2 px-3 py-2 border-b border-gray-100 dark:border-gray-700"
67
+ className: "flex items-center gap-2 px-3 py-2.5 border-b border-gray-100 dark:border-gray-700/80"
55
68
  }, React.createElement("span", {
56
- className: "flex-shrink-0 text-blue-500"
57
- }, meta.icon), React.createElement("p", {
58
- className: "flex-1 min-w-0 text-sm font-semibold text-gray-900 dark:text-gray-100 truncate"
69
+ className: "flex-shrink-0 inline-flex items-center gap-1 rounded-md bg-blue-50 dark:bg-gray-700 px-1.5 py-0.5 text-[11px] font-medium text-blue-600 dark:text-blue-400"
70
+ }, kindIcon, kindLabel), React.createElement("p", {
71
+ className: "flex-1 min-w-0 text-sm font-semibold text-gray-900 dark:text-gray-100 truncate",
72
+ title: title
59
73
  }, title), attribution && React.createElement("span", {
60
- className: "flex-shrink-0 text-[10px] text-gray-400 dark:text-gray-500 truncate max-w-[55%]"
74
+ className: "flex-shrink-0 text-[11px] text-gray-400 dark:text-gray-500 truncate max-w-[55%]"
61
75
  }, attribution)), React.createElement("div", {
62
- className: `p-2 ${isGrid ? 'grid grid-cols-2 gap-2' : 'flex flex-col gap-2'}`,
76
+ className: `p-2 ${isImageGrid ? 'grid grid-cols-2 gap-2' : 'flex flex-col gap-2'}`,
63
77
  onClick: open,
64
78
  role: onMessageClick ? 'button' : undefined
65
79
  }, items.map((item, i) => React.createElement(MediaAsset, {
66
80
  key: (item.url ?? '') + i,
67
- item: item
68
- }))), React.createElement("div", {
69
- className: "px-3 pb-3 pt-1"
81
+ item: item,
82
+ onMeta: items.length === 1 ? setAssetMeta : undefined
83
+ }))), metaCells.length > 0 && React.createElement("div", {
84
+ className: "px-3 pb-1 flex flex-wrap items-center gap-2 text-[11px] text-gray-500 dark:text-gray-400"
85
+ }, metaCells.map((cell, i) => React.createElement(React.Fragment, {
86
+ key: cell + i
87
+ }, i > 0 && React.createElement("span", {
88
+ className: "text-gray-300 dark:text-gray-600"
89
+ }, "\u00B7"), React.createElement(MetaCell, null, cell)))), React.createElement("div", {
90
+ className: "px-3 pb-3 pt-2"
70
91
  }, attachment.prompt && React.createElement("p", {
71
92
  className: "text-xs text-gray-500 dark:text-gray-400 line-clamp-2",
72
93
  title: attachment.prompt
73
94
  }, attachment.prompt), React.createElement("div", {
74
- className: "mt-2 flex items-center gap-3"
95
+ className: "mt-2 flex flex-wrap items-center gap-2"
75
96
  }, React.createElement("a", {
76
97
  href: attachment.url,
77
98
  target: "_blank",
78
99
  rel: "noopener noreferrer",
79
100
  onClick: e => e.stopPropagation(),
80
- className: "inline-flex items-center gap-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:underline"
81
- }, "Open ", React.createElement(FiExternalLink, {
82
- className: "w-3 h-3"
83
- })), React.createElement("a", {
101
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-blue-600 hover:bg-blue-700 px-2.5 py-1.5 text-xs font-medium text-white transition-colors"
102
+ }, React.createElement(FiExternalLink, {
103
+ className: "w-3.5 h-3.5"
104
+ }), " Open"), React.createElement("a", {
84
105
  href: attachment.url,
85
106
  download: attachment.fileName || true,
86
107
  onClick: e => e.stopPropagation(),
87
- className: "inline-flex items-center gap-1 text-xs font-medium text-gray-600 dark:text-gray-300 hover:underline"
88
- }, "Download ", React.createElement(FiDownload, {
89
- className: "w-3 h-3"
90
- })))));
108
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-200 transition-colors"
109
+ }, React.createElement(FiDownload, {
110
+ className: "w-3.5 h-3.5"
111
+ }), " Download"), React.createElement("button", {
112
+ type: "button",
113
+ onClick: e => {
114
+ e.stopPropagation();
115
+ void copyLink();
116
+ },
117
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-200 transition-colors"
118
+ }, copied ? React.createElement(FiCheck, {
119
+ className: "w-3.5 h-3.5"
120
+ }) : React.createElement(FiCopy, {
121
+ className: "w-3.5 h-3.5"
122
+ }), copied ? 'Copied' : 'Copy link'))));
91
123
  };export{MessageMedia};//# sourceMappingURL=MessageMedia.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageMedia.js","sources":["../../../src/components/MessageWidgets/MessageMedia.tsx"],"sourcesContent":[null],"names":[],"mappings":"qZAiBA,MAAM,SAAS,GAAwE;AACnF,EAAA,KAAA,EAAK;AACL,IAAA,IAAA,EAAK,KAAI,CAAA,uBAAO;AAChB,MAAA,SAAS,EAAI;KACf,CAAA;AAEF,IAAA,KAAA,EAAA;;;;;;AAMG,IAAA,KAAA,EAAA;AACH,GAAA;OACI,EAAA;AACA,IAAA,IAAA,EAAM,KAAE,CAAA,aAAgB,CAAA,OAAQ,EAAA;AAIhC,MAAA,SAAK,EAAA;AAA+B,KAAA,CAAA;AAEpC,IAAA,KAAA,EAAM;;AAGE,CAAA;AACM;;;;;;AAMC;AACH,MAAA,YAAA,GAAA,KAAA,IAAA;AAEZ,EAAA,MAAA;IACA,OAAM;IACN,KAAA;AACA,IAAA;MAEA,KAAM;QAEN;;AAOY,GAAA,GAAA,OAAA,CAAA,kBAAA,EAAA,QAAM;AACN,EAAA,IAAA,CAAA,UAAA,IAAA,CAAA,UAAA,CAAA,GAAA,EAAA,OAAG,IAAS;aAGX,GAAA,UAAW,CAAI,KACZ,IAAA,UAAA,CAAA,KAAA,CAAA,MAAM,GAAA,CAAA,GAAA;AAOd,IAAA,IAAA,EAAA,UAAA,CAAA,IAAA;SAWA,UAAA,CAAA,GAAA;AACK,IAAA,KAAA,EAAA,UAAA,CAAU,KAAC;YAKZ,EAAA,UAAA,CAAA,QAAA;AACI,IAAA,SAAA,EAAA,UAAA,CAAA;;AAOS,EAAA,MAAA,IAAA,GAAA,SAAA,CAAA,UAAA,CAAA,IAAC,cAAc,CAAA,KAAC;AAEzB,EAAA,MAAA,KAAA,GAAA,UAAA,CAAA,KAAA,IAAA,CAAA,EAAA,IACI,CAAA,KAAM,CAAA,UAAW,CAAA;;sBAKR,CAAA,MAAA,GAAA,CAAA,IAAA,UAAC,CAAA,IAAU,KAAC,OAAS;AAMtD,EAAE,MAAA,IAAA,GAAA,MAAA,cAAA,GAAA,OAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"MessageMedia.js","sources":["../../../src/components/MessageWidgets/MessageMedia.tsx"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"MessageTravel.d.ts","sourceRoot":"","sources":["../../../src/components/MessageWidgets/MessageTravel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAG/B,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CACzC;AAuBD;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,mBAAmB,sBAuHvD,CAAC"}
1
+ {"version":3,"file":"MessageTravel.d.ts","sourceRoot":"","sources":["../../../src/components/MessageWidgets/MessageTravel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAK/B,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CACzC;AAuBD;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,mBAAmB,sBA4HvD,CAAC"}
@@ -1,4 +1,4 @@
1
- import React from'react';import {FiMapPin}from'@react-icons/all-files/fi/FiMapPin.js';import {FiStar}from'@react-icons/all-files/fi/FiStar.js';import {FiChevronRight}from'@react-icons/all-files/fi/FiChevronRight.js';const LIST_PREVIEW = 5;
1
+ import React from'react';import {FiMapPin}from'@react-icons/all-files/fi/FiMapPin.js';import {FiStar}from'@react-icons/all-files/fi/FiStar.js';import {FiChevronRight}from'@react-icons/all-files/fi/FiChevronRight.js';import {MessageFlights}from'./MessageFlights.js';import {isFlightAttachment}from'@admin-layout/client';const LIST_PREVIEW = 5;
2
2
  const formatPrice = p => p.priceLabel ?? (typeof p.price === 'number' ? `$${Math.round(p.price)}` : undefined);
3
3
  const formatSource = source => {
4
4
  if (!source) return undefined;
@@ -27,6 +27,14 @@ const MessageTravel = props => {
27
27
  if (!attachment || !Array.isArray(attachment.properties) || attachment.properties.length === 0) {
28
28
  return null;
29
29
  }
30
+ // Flight offers render as their own card row; everything else is a stay.
31
+ if (isFlightAttachment(attachment)) {
32
+ return React.createElement(MessageFlights, {
33
+ message: message,
34
+ attachment: attachment,
35
+ onMessageClick: onMessageClick
36
+ });
37
+ }
30
38
  const properties = attachment.properties;
31
39
  const count = attachment.totalCount ?? properties.length;
32
40
  const city = attachment.city || properties.find(p => p.city)?.city;