@admin-layout/ai-chat-ext-browser 12.2.4-alpha.85 → 12.2.5-alpha.12

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 (86) hide show
  1. package/lib/components/MessageWidgets/MessageFlights.d.ts +47 -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 -83
  8. package/lib/components/MessageWidgets/MessageMedia.js.map +1 -1
  9. package/lib/components/MessageWidgets/MessageResults.d.ts +54 -0
  10. package/lib/components/MessageWidgets/MessageResults.d.ts.map +1 -0
  11. package/lib/components/MessageWidgets/MessageResults.js +163 -0
  12. package/lib/components/MessageWidgets/MessageResults.js.map +1 -0
  13. package/lib/components/MessageWidgets/MessageTravel.d.ts +2 -0
  14. package/lib/components/MessageWidgets/MessageTravel.d.ts.map +1 -1
  15. package/lib/components/MessageWidgets/MessageTravel.js +22 -2
  16. package/lib/components/MessageWidgets/MessageTravel.js.map +1 -1
  17. package/lib/components/MessageWidgets/ResultCards.d.ts +19 -0
  18. package/lib/components/MessageWidgets/ResultCards.d.ts.map +1 -0
  19. package/lib/components/MessageWidgets/ResultCards.js +543 -0
  20. package/lib/components/MessageWidgets/ResultCards.js.map +1 -0
  21. package/lib/components/MessageWidgets/ResultRow.d.ts +57 -0
  22. package/lib/components/MessageWidgets/ResultRow.d.ts.map +1 -0
  23. package/lib/components/MessageWidgets/ResultRow.js +216 -0
  24. package/lib/components/MessageWidgets/ResultRow.js.map +1 -0
  25. package/lib/components/MessageWidgets/index.d.ts +7 -0
  26. package/lib/components/MessageWidgets/index.d.ts.map +1 -1
  27. package/lib/components/MessageWidgets/mediaUtils.d.ts +21 -0
  28. package/lib/components/MessageWidgets/mediaUtils.d.ts.map +1 -0
  29. package/lib/components/MessageWidgets/mediaUtils.js +76 -0
  30. package/lib/components/MessageWidgets/mediaUtils.js.map +1 -0
  31. package/lib/components/index.d.ts +2 -1
  32. package/lib/components/index.d.ts.map +1 -1
  33. package/lib/index.d.ts +7 -0
  34. package/lib/index.d.ts.map +1 -1
  35. package/lib/index.js +1 -1
  36. package/lib/interfaces/ai-chat.interface.d.ts +13 -14
  37. package/lib/interfaces/ai-chat.interface.d.ts.map +1 -1
  38. package/lib/interfaces/index.d.ts +1 -1
  39. package/lib/interfaces/index.d.ts.map +1 -1
  40. package/lib/module.d.ts.map +1 -1
  41. package/lib/module.js +13 -1
  42. package/lib/module.js.map +1 -1
  43. package/lib/slot-fill/chat-settings-fill.js +1 -1
  44. package/lib/slot-fill/chat-settings-fill.js.map +1 -1
  45. package/lib/slot-fill/index.d.ts +4 -0
  46. package/lib/slot-fill/index.d.ts.map +1 -1
  47. package/lib/slot-fill/message-ai-assistant-fill.js +1 -1
  48. package/lib/slot-fill/message-ai-assistant-fill.js.map +1 -1
  49. package/lib/slot-fill/message-codeblock-fill.js +1 -1
  50. package/lib/slot-fill/message-codeblock-fill.js.map +1 -1
  51. package/lib/slot-fill/message-connector-fill.js +1 -1
  52. package/lib/slot-fill/message-connector-fill.js.map +1 -1
  53. package/lib/slot-fill/message-generic-fill.js +1 -1
  54. package/lib/slot-fill/message-generic-fill.js.map +1 -1
  55. package/lib/slot-fill/message-jobs-fill.d.ts +4 -0
  56. package/lib/slot-fill/message-jobs-fill.d.ts.map +1 -0
  57. package/lib/slot-fill/message-jobs-fill.js +6 -0
  58. package/lib/slot-fill/message-jobs-fill.js.map +1 -0
  59. package/lib/slot-fill/message-media-fill.js +1 -1
  60. package/lib/slot-fill/message-media-fill.js.map +1 -1
  61. package/lib/slot-fill/message-movies-fill.d.ts +4 -0
  62. package/lib/slot-fill/message-movies-fill.d.ts.map +1 -0
  63. package/lib/slot-fill/message-movies-fill.js +6 -0
  64. package/lib/slot-fill/message-movies-fill.js.map +1 -0
  65. package/lib/slot-fill/message-resume-fill.d.ts +4 -0
  66. package/lib/slot-fill/message-resume-fill.d.ts.map +1 -0
  67. package/lib/slot-fill/message-resume-fill.js +6 -0
  68. package/lib/slot-fill/message-resume-fill.js.map +1 -0
  69. package/lib/slot-fill/message-service-fill.js +1 -1
  70. package/lib/slot-fill/message-service-fill.js.map +1 -1
  71. package/lib/slot-fill/message-shopping-fill.d.ts +4 -0
  72. package/lib/slot-fill/message-shopping-fill.d.ts.map +1 -0
  73. package/lib/slot-fill/message-shopping-fill.js +6 -0
  74. package/lib/slot-fill/message-shopping-fill.js.map +1 -0
  75. package/lib/slot-fill/message-thinking-fill.js +1 -1
  76. package/lib/slot-fill/message-thinking-fill.js.map +1 -1
  77. package/lib/slot-fill/message-tooluse-fill.js +1 -1
  78. package/lib/slot-fill/message-tooluse-fill.js.map +1 -1
  79. package/lib/slot-fill/message-travel-fill.js +1 -1
  80. package/lib/slot-fill/message-travel-fill.js.map +1 -1
  81. package/lib/slot-fill/message-workflow-fill.js +1 -1
  82. package/lib/slot-fill/message-workflow-fill.js.map +1 -1
  83. package/lib/utils/parseWidgetPost.d.ts.map +1 -1
  84. package/lib/utils/parseWidgetPost.js +1 -1
  85. package/lib/utils/parseWidgetPost.js.map +1 -1
  86. package/package.json +7 -4
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { IPost } from 'common';
3
+ import { TravelAttachmentsInterface } from '../../interfaces';
4
+ import { FlightCardView, FlightSliceView, 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
+ /** Soft monogram tile colours, indexed by `airlineTone`; shared by every result card. */
12
+ export declare const TONES: string[];
13
+ /**
14
+ * The route between the two times: duration above, a line with one amber dot
15
+ * per stop and the plane at the arrival end, stops below.
16
+ */
17
+ export declare const RouteLine: ({ slice }: {
18
+ slice: FlightSliceView;
19
+ }) => React.JSX.Element;
20
+ export interface IFlightCardProps {
21
+ card: FlightCardView;
22
+ onOpen?: () => void;
23
+ fullWidth?: boolean;
24
+ /** Show the departure date on the card (offers on different dates, or a round trip). */
25
+ showDate?: boolean;
26
+ /** Briefly outlined, e.g. after a quick-pick chip jumped to it. */
27
+ highlighted?: boolean;
28
+ }
29
+ export declare const FlightCard: ({ card, onOpen, fullWidth, showDate, highlighted, }: IFlightCardProps) => React.JSX.Element;
30
+ export interface IFlightCardListProps {
31
+ attachment: TravelAttachmentsInterface;
32
+ /** Fired by a card with no seller link; omit it to render such cards as plain content. */
33
+ onOpen?: () => void;
34
+ /** Ordering; `recommended` (default) keeps the agent's order. */
35
+ sort?: FlightSort;
36
+ }
37
+ /** Every flight as a full-width vertical list, for a side panel or a results page. */
38
+ export declare const FlightCardList: ({ attachment, onOpen, sort }: IFlightCardListProps) => React.JSX.Element;
39
+ /**
40
+ * Flight results as a swipeable row of cards: one card per offer, the next
41
+ * one peeking in so the row reads as scrollable. Above it, the route and date
42
+ * once, and quick picks (cheapest, fastest, best value) that jump to their
43
+ * card. Touch swipes and snaps; a pointer gets arrow buttons, since sideways
44
+ * scrolling is awkward with a mouse.
45
+ */
46
+ export declare const MessageFlights: ({ message, attachment, onMessageClick }: IMessageFlightsProps) => React.JSX.Element;
47
+ //# 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,EACd,eAAe,EACf,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;AAED,yFAAyF;AACzF,eAAO,MAAM,KAAK,UASjB,CAAC;AA4BF;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,WAAW;IAAE,KAAK,EAAE,eAAe,CAAA;CAAE,sBAiC9D,CAAC;AAwCF,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 monogram tile colours, indexed by `airlineTone`; shared by every result card. */
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,RouteLine,TONES};//# sourceMappingURL=MessageFlights.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageFlights.js","sources":["../../../src/components/MessageWidgets/MessageFlights.tsx"],"sourcesContent":[null],"names":[],"mappings":"ibAwBA;AACO,MAAM,KAAK,GAAG,CAAA,8DAAA,EAAA,0EAAA,EAAA,8EAAA,EAAA,sEAAA,EAAA,kEAAA,EAAA,0EAAA,EAAA,kEAAA,EAAA,0EAAA;MACjB,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;;AAGC,MAAA,SAAe,GAAA,CAAK;AAQ1B,EAAA;;;AAGG,EAAA,OAAA,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA;AACH,IAAA,SAAa,EAAA,qDAAsD;AAC/D,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;AAG/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;AAmCD;;;;;;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,58 +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';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
- }
20
- };
21
- /** Render a single asset with the element that matches its kind. */
22
- const MediaAsset = ({
23
- item
24
- }) => {
25
- if (item.kind === 'video') {
26
- return React.createElement("video", {
27
- src: item.url,
28
- poster: item.posterUrl,
29
- controls: true,
30
- preload: "metadata",
31
- className: "w-full max-h-80 rounded-lg bg-black object-contain"
32
- });
33
- }
34
- if (item.kind === 'audio') {
35
- return React.createElement("audio", {
36
- src: item.url,
37
- controls: true,
38
- preload: "metadata",
39
- className: "w-full"
40
- });
41
- }
42
- // eslint-disable-next-line @next/next/no-img-element
43
- return React.createElement("img", {
44
- src: item.url,
45
- alt: item.title || 'Generated image',
46
- loading: "lazy",
47
- className: "w-full max-h-80 rounded-lg object-contain bg-gray-50 dark:bg-gray-900"
48
- });
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
+ })
49
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);
50
18
  /**
51
- * Media card — renders the image / video / audio a cdecli generation connector
52
- * (yantra-image-generation, yantra-video-generation, audio / TTS, …) returned,
53
- * inline in the chat. Shows the asset(s), the prompt as a caption, an
54
- * attribution line (connector · model), and open / download actions. The asset
55
- * 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.
56
26
  */
57
27
  const MessageMedia = props => {
58
28
  const {
@@ -63,58 +33,91 @@ const MessageMedia = props => {
63
33
  const {
64
34
  attachment
65
35
  } = message.propsConfiguration?.contents ?? {};
36
+ const [assetMeta, setAssetMeta] = useState({});
37
+ const [copied, setCopied] = useState(false);
66
38
  if (!attachment || !attachment.url) return null;
67
- const items = attachment.items && attachment.items.length > 0 ? attachment.items : [{
68
- kind: attachment.kind,
69
- url: attachment.url,
70
- title: attachment.title,
71
- mimeType: attachment.mimeType,
72
- posterUrl: attachment.posterUrl
73
- }];
74
- const meta = KIND_META[attachment.kind] ?? KIND_META.image;
75
- const title = attachment.title || `${meta.label} generated`;
76
- const attribution = [attachment.connector, attachment.model].filter(Boolean).join(' · ');
77
- 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);
78
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
+ };
79
63
  return React.createElement("div", {
80
64
  key: `${index}`,
81
- 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"
82
66
  }, React.createElement("div", {
83
- 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"
84
68
  }, React.createElement("span", {
85
- className: "flex-shrink-0 text-blue-500"
86
- }, meta.icon), React.createElement("p", {
87
- 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
88
73
  }, title), attribution && React.createElement("span", {
89
- 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%]"
90
75
  }, attribution)), React.createElement("div", {
91
- 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'}`,
92
77
  onClick: open,
93
78
  role: onMessageClick ? 'button' : undefined
94
79
  }, items.map((item, i) => React.createElement(MediaAsset, {
95
80
  key: (item.url ?? '') + i,
96
- item: item
97
- }))), React.createElement("div", {
98
- 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"
99
91
  }, attachment.prompt && React.createElement("p", {
100
92
  className: "text-xs text-gray-500 dark:text-gray-400 line-clamp-2",
101
93
  title: attachment.prompt
102
94
  }, attachment.prompt), React.createElement("div", {
103
- className: "mt-2 flex items-center gap-3"
95
+ className: "mt-2 flex flex-wrap items-center gap-2"
104
96
  }, React.createElement("a", {
105
97
  href: attachment.url,
106
98
  target: "_blank",
107
99
  rel: "noopener noreferrer",
108
100
  onClick: e => e.stopPropagation(),
109
- className: "inline-flex items-center gap-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:underline"
110
- }, "Open ", React.createElement(FiExternalLink, {
111
- className: "w-3 h-3"
112
- })), 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", {
113
105
  href: attachment.url,
114
106
  download: attachment.fileName || true,
115
107
  onClick: e => e.stopPropagation(),
116
- className: "inline-flex items-center gap-1 text-xs font-medium text-gray-600 dark:text-gray-300 hover:underline"
117
- }, "Download ", React.createElement(FiDownload, {
118
- className: "w-3 h-3"
119
- })))));
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'))));
120
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":"oVAgBA,MAAM,SAAS,GAAwE;AACnF,EAAA,KAAA,EAAK;AACL,IAAA,IAAA,EAAK,KAAI,CAAA,uBAAO;AAChB,MAAA,SAAS,EAAI;KACf,CAAA;AAEF,IAAA,KAAA,EAAA;AACA,GAAA;AACI,EAAA,KAAA;QACI,EAAA,KAAO,CACH,aAAA,CAAA,OAAA,EAAA;MAQP,SAAA,EAAA;AACD,KAAA,CAAA;AACI,IAAA,KAAA,EAAA;;OAEJ,EAAA;IACA,IAAA,EAAA,KACI,CAAA,aAAA,CAAA,OAAA,EAAA;AAOR,MAAE,SAAA,EAAA;AAEF,KAAA,CAAA;;;;;;AAMG,EAAA;AACH,CAAA,KAAM;MACF,IAAM,CAAA,SAAS,SAAS;AACxB,IAAA,OAAM,KAAE,CAAA,aAAgB,CAAA,OAAQ,EAAA;AAIhC,MAAA,GAAK,EAAA,IAAA,CAAA,GAAU;AAAqB,MAAA,MAAA,EAAA,KAAW,SAAC;AAEhD,MAAA,QAAM,EAAK,IACP;aACM,EAAA,UAAW;AACb,MAAA,SAAE,EAAA;AACI,KAAA,CAAA;;oBAEO,OAAE,EAAA;gBACL,CAAA,aAAO,CAAA,OAAW,EAAK;gBACvB,GAAA;gBACA,IAAA;AACH,MAAA,OAAA,EAAA,UAAA;eACH,EAAA;AAEZ,KAAA,CAAA;;;AAGA,EAAA,OAAA,KAAM,CAAA,aAAe,CAAA,KAAS,EAAC;IAE/B,GAAA,EAAA,IAAM,CAAI,GAAG;IAEb,GAAA,EAAA,IACI,CAAA,KAAA,IAAA,iBAAA;WAKI,EAAA,MAAA;AACI,IAAA,SAAA,EAAA;AACA,GAAA,CAAA;;AAWJ;;AAYK;;AAMG;;AAOS;AAET,MAAA,YAAA,GAAA,KAAA,IAAA;;;AAYpB,IAAE,KAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"MessageMedia.js","sources":["../../../src/components/MessageWidgets/MessageMedia.tsx"],"sourcesContent":[null],"names":[],"mappings":"upBA4BA,MAAM,SAAS,GAAuC;AAClD,EAAA,KAAA,EAAK,KAAE,CAAA,aAAA,CAAA,OAAC,EAAO;AACf,IAAA,SAAO,EAAA;AACP,GAAA,CAAA;EACF,KAAA,EAAA,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA;AAEF,IAAA,SAAA,EAAA;AACA,GAAA,CAAA;AAIA,EAAA,KAAA,EAAA,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA;;;;;;;;AAQG,EAAA,SAAA,EAAA;AACH,CAAA,EAAA,QAAO,CAAA;;AAEH;;;AAOA;AAAoC;AAEpC;AACA;AAAwB;AAElB,MAAA,YAAO,GAAW,KAAK,IAAA;QAC7B;IACA,OAAM;IACN,KAAA;AACA,IAAA;MACA,KAAM;QAEN;IACA;MACA,OAAM,CAAA,kBAAc,EAAA;AACpB,EAAA,MAAA,CAAA,SAAc,EAAA,YAAI,CAAW,GAAG,QAAA,CAAA,EAAA,CAAA;AAChC,EAAA,MAAA,CAAA,MAAM,EAAM,SAAG,CAAA,GAAA,QAAkB,CAAA,KAAA,CAAA;AACjC,EAAA,IAAA,CAAA,cAAe,CAAG,UAAA,CAAA,GAAA,EAAA,OAAA,IAAA;QACd,KAAA,GAAA,gBAAuB,CAAA,UAAM,CAAA;WAC7B,CAAA,MAAU,KAAA,CAAA,EAAA,OAAA,IAAA;QACV,IAAA,GAAA,UAAQ,CAAA,IAAA;QACR,SAAM,GAAA,gBAAA,CAAA,IAAA,CAAA,IAAA,gBAAA,CAAA,KAAA;QACN,QAAA,GAAW,SAAA,CAAA,IAAA,CAAA,IAAA,SAAA,CAAA,KAAA;AACd,EAAA,MAAC,KAAO,GAAA,UAAqB,CAAA,KAAA,IAAA,CAAA,EAAA,SAAA,CAAA,UAAA,CAAA;QAE9B,WAAa,GAAK,iBAAiB,CAAC,UAAS,CAAA;AAE7C,EAAA,MAAA,WAAc,GAAG,KAAK,CAAA,MAAM,GAAA,CAAA,IAAA,IAAA,KAAA,OAAA;AACxB;;kBAEI,GAAS,CAAC,WAAM,GAAA,gBAAA,CAAA,SAAA,CAAA,KAAA,EAAA,SAAA,CAAA,MAAA,CAAA,GAAA,SAAA;AAChB,EAAA,MAAA,QAAA,GAAO,CAAA,WAAW,GAAG,cAAa,CAAA,SAAY,CAAC,QAAC,CAAA,GAAA,SAAA;QACpD,MAAC,GAAA,iBAAA,CAAA,UAAA,CAAA,QAAA,EAAA,UAAA,CAAA,QAAA,CAAA;AAAC,EAAA,MAAA,SAAO,GAAA,CAAA,WAAA,GAAA,CAAA,EAAA,KAAA,CAAA,MAAA,CAAA,OAAA,CAAA,GAAA,SAAA,EAAA,UAAA,EAAA,QAAA,EAAA,MAAA,EAAA,WAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA;YACL,GAAA,MAAA,cAAA,GAAA,OAAA,CAAA;QACJ,QAAC,GAAA,YAAA;AACL,IAAA,IAAE;MAEF,MACI,SAAA,CAAA,SAAA,EAAA,SACI,CAAA,WAAa,GAAE,CACf;eAGA,CAAA,IAAA,CAAA;YACI,CAAA,UAAA,CAAA,MAAA,SAAA,CAAA,KAAM,CAAA,EAAA,KAAU;;AAEX;;;AAgBT,EAAA,OAAA,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA;AAeC,IAAA,GAAA,EAAA,CAAA,EAAA,KAAS,CAAC,CAAA;AAIM,IAAA,SAAC;wBACF,CAAA,KAAA,EAAC;aAOjB,EAAA;AACK,GAAA,EAAA,KAAA,CAAA,aAAW,CAAA,MAAM,EAAI;aAKtB,EAAA;AACI,GAAA,EAAA,QAAA,EAAA,SAAA,CAAA,EAAA,KAAA,CAAA,aACI,CAAI,GAAE,EAAA;AAMN,IAAA,SAAA,EAAA,gFAA0C;AAC1C,IAAA,KAAA,EAAA;AACJ,GAAA,EAAA,KAAA,CAAA,EAAA,WAAA,IAAA,KAAA,CAAA,aACQ,CAAA,MAAE,EAAA;AAKN,IAAA,SAAA,EAAA;AACA,GAAA,EAAA,WAAA,CAAA,CAAA,EAAA,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA;gBACJ,IAAA,EAAA,WAAA,GAAA,wBACS,GAAQ,qBACE,CAAA,CAAA;;wBAEX,GAAA,QAAK;oBACT,EAAC,CACD,KAAA,KAAS,CAAC,aAAA,CAAA,UAAA,EAAA;AAET,IAAA,GAAA,EAAA,CAAA,IAAA,CAAA,GAAA,IAAA,EAAA,IAAQ,CAAC;AACT,IAAA,IAAA,EAAA,IAAA;AAMzB,IAAE,MAAA,EAAA,KAAA,CAAA,MAAA,KAAA,CAAA,GAAA,YAAA,GAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}