@mastra/playground-ui 68.0.0-alpha.3 → 68.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{FilterBar-CQl04O6t.cjs → FilterBar-BSP2UTjc.cjs} +2 -2
- package/dist/{FilterBar-CQl04O6t.cjs.map → FilterBar-BSP2UTjc.cjs.map} +1 -1
- package/dist/{FilterBar-BgW2dqRQ.js → FilterBar-PSQ1OwqH.js} +2 -2
- package/dist/{FilterBar-BgW2dqRQ.js.map → FilterBar-PSQ1OwqH.js.map} +1 -1
- package/dist/{MetricsCard-CEB-f2sv.cjs → MetricsCard-BEwiS7Pe.cjs} +8 -8
- package/dist/MetricsCard-BEwiS7Pe.cjs.map +1 -0
- package/dist/{MetricsCard-M8awJ2B_.js → MetricsCard-ua6C6b8a.js} +10 -10
- package/dist/MetricsCard-ua6C6b8a.js.map +1 -0
- package/dist/{MetricsLineChart-BhHoduO0.js → MetricsLineChart-4SwxBbQx.js} +14 -3
- package/dist/MetricsLineChart-4SwxBbQx.js.map +1 -0
- package/dist/{MetricsLineChart-CjBm4IXu.cjs → MetricsLineChart-tZxsauau.cjs} +13 -2
- package/dist/MetricsLineChart-tZxsauau.cjs.map +1 -0
- package/dist/{MetricsStackedBarChart-BdMlKv6G.cjs → MetricsStackedBarChart-CDLE76IB.cjs} +2 -2
- package/dist/{MetricsStackedBarChart-BdMlKv6G.cjs.map → MetricsStackedBarChart-CDLE76IB.cjs.map} +1 -1
- package/dist/{MetricsStackedBarChart-BRuA6MTE.js → MetricsStackedBarChart-D2QyS4-z.js} +2 -2
- package/dist/{MetricsStackedBarChart-BRuA6MTE.js.map → MetricsStackedBarChart-D2QyS4-z.js.map} +1 -1
- package/dist/components/DateTimeRangePicker.cjs.js +1 -1
- package/dist/components/DateTimeRangePicker.es.js +1 -1
- package/dist/components/FilterBar.cjs.js +2 -2
- package/dist/components/FilterBar.es.js +2 -2
- package/dist/components/MetricsCard.cjs.js +1 -1
- package/dist/components/MetricsCard.es.js +1 -1
- package/dist/components/MetricsLineChart.cjs.js +1 -1
- package/dist/components/MetricsLineChart.es.js +1 -1
- package/dist/components/MetricsStackedBarChart.cjs.js +1 -1
- package/dist/components/MetricsStackedBarChart.es.js +1 -1
- package/dist/{date-time-range-picker-CTt7Od0P.cjs → date-time-range-picker-CWGjoR3j.cjs} +40 -13
- package/dist/date-time-range-picker-CWGjoR3j.cjs.map +1 -0
- package/dist/{date-time-range-picker-DKh1K9Iy.js → date-time-range-picker-CdSiXwB4.js} +40 -13
- package/dist/date-time-range-picker-CdSiXwB4.js.map +1 -0
- package/dist/domains/chat/messages/message-row.cjs.js +1 -1
- package/dist/domains/chat/messages/message-row.es.js +1 -1
- package/dist/domains/chat/tools/badges/agent-badge-wrapper.cjs.js +1 -1
- package/dist/domains/chat/tools/badges/agent-badge-wrapper.es.js +1 -1
- package/dist/domains/chat/tools/badges/agent-badge.cjs.js +1 -1
- package/dist/domains/chat/tools/badges/agent-badge.es.js +1 -1
- package/dist/domains/chat/tools/connect-prompt-tool.cjs.js +79 -4
- package/dist/domains/chat/tools/connect-prompt-tool.cjs.js.map +1 -1
- package/dist/domains/chat/tools/connect-prompt-tool.es.js +79 -4
- package/dist/domains/chat/tools/connect-prompt-tool.es.js.map +1 -1
- package/dist/domains/chat/tools/mcp-input-request-tool.cjs.js +10 -12
- package/dist/domains/chat/tools/mcp-input-request-tool.cjs.js.map +1 -1
- package/dist/domains/chat/tools/mcp-input-request-tool.es.js +10 -12
- package/dist/domains/chat/tools/mcp-input-request-tool.es.js.map +1 -1
- package/dist/domains/chat/tools/tool-card-kind.cjs.js +1 -1
- package/dist/domains/chat/tools/tool-card-kind.cjs.js.map +1 -1
- package/dist/domains/chat/tools/tool-card-kind.es.js +1 -1
- package/dist/domains/chat/tools/tool-card-kind.es.js.map +1 -1
- package/dist/domains/chat/tools/tool-card.cjs.js +1 -1
- package/dist/domains/chat/tools/tool-card.es.js +1 -1
- package/dist/domains/connect/types.cjs.js.map +1 -1
- package/dist/domains/connect/types.es.js.map +1 -1
- package/dist/domains/connect/use-user-connect.cjs.js +19 -2
- package/dist/domains/connect/use-user-connect.cjs.js.map +1 -1
- package/dist/domains/connect/use-user-connect.es.js +19 -2
- package/dist/domains/connect/use-user-connect.es.js.map +1 -1
- package/dist/domains/dashboards/components/dashboard-renderer.cjs.js +4 -3
- package/dist/domains/dashboards/components/dashboard-renderer.cjs.js.map +1 -1
- package/dist/domains/dashboards/components/dashboard-renderer.es.js +4 -3
- package/dist/domains/dashboards/components/dashboard-renderer.es.js.map +1 -1
- package/dist/domains/dashboards/components/widget-chrome.cjs.js +1 -1
- package/dist/domains/dashboards/components/widget-chrome.es.js +1 -1
- package/dist/domains/dashboards/components/widget-error.cjs.js +1 -1
- package/dist/domains/dashboards/components/widget-error.es.js +1 -1
- package/dist/domains/dashboards/hooks/use-widget-views.cjs.js +4 -3
- package/dist/domains/dashboards/hooks/use-widget-views.cjs.js.map +1 -1
- package/dist/domains/dashboards/hooks/use-widget-views.es.js +4 -3
- package/dist/domains/dashboards/hooks/use-widget-views.es.js.map +1 -1
- package/dist/domains/dashboards/kinds/scores/scores-widget.cjs.js +9 -6
- package/dist/domains/dashboards/kinds/scores/scores-widget.cjs.js.map +1 -1
- package/dist/domains/dashboards/kinds/scores/scores-widget.es.js +9 -6
- package/dist/domains/dashboards/kinds/scores/scores-widget.es.js.map +1 -1
- package/dist/domains/dashboards/kinds/table/table-tabs.cjs.js +2 -2
- package/dist/domains/dashboards/kinds/table/table-tabs.es.js +2 -2
- package/dist/domains/dashboards/kinds/table/table-widget.cjs.js +1 -1
- package/dist/domains/dashboards/kinds/table/table-widget.es.js +1 -1
- package/dist/domains/dashboards/kinds/timeseries/timeseries-widget.cjs.js +6 -5
- package/dist/domains/dashboards/kinds/timeseries/timeseries-widget.cjs.js.map +1 -1
- package/dist/domains/dashboards/kinds/timeseries/timeseries-widget.es.js +6 -5
- package/dist/domains/dashboards/kinds/timeseries/timeseries-widget.es.js.map +1 -1
- package/dist/domains/dashboards/lib/chrome.cjs.js +9 -2
- package/dist/domains/dashboards/lib/chrome.cjs.js.map +1 -1
- package/dist/domains/dashboards/lib/chrome.es.js +9 -2
- package/dist/domains/dashboards/lib/chrome.es.js.map +1 -1
- package/dist/domains/dashboards/widget-kind.cjs.js.map +1 -1
- package/dist/domains/dashboards/widget-kind.es.js.map +1 -1
- package/dist/domains/metrics/components/agent-runs-card.cjs.js +3 -3
- package/dist/domains/metrics/components/agent-runs-card.es.js +3 -3
- package/dist/domains/metrics/components/breakdown/breakdown-tab-body.cjs.js +1 -1
- package/dist/domains/metrics/components/breakdown/breakdown-tab-body.es.js +1 -1
- package/dist/domains/metrics/components/breakdown-card.cjs.js +1 -1
- package/dist/domains/metrics/components/breakdown-card.es.js +1 -1
- package/dist/domains/metrics/components/chart-area.cjs.js +1 -1
- package/dist/domains/metrics/components/chart-area.es.js +1 -1
- package/dist/domains/metrics/components/chart-card-error.cjs.js +1 -1
- package/dist/domains/metrics/components/chart-card-error.es.js +1 -1
- package/dist/domains/metrics/components/chart-card.cjs.js +1 -1
- package/dist/domains/metrics/components/chart-card.es.js +1 -1
- package/dist/domains/metrics/components/date-range-selector.cjs.js +1 -1
- package/dist/domains/metrics/components/date-range-selector.es.js +1 -1
- package/dist/domains/metrics/components/distinct-ids/distinct-ids-chart.cjs.js +3 -2
- package/dist/domains/metrics/components/distinct-ids/distinct-ids-chart.cjs.js.map +1 -1
- package/dist/domains/metrics/components/distinct-ids/distinct-ids-chart.es.js +3 -2
- package/dist/domains/metrics/components/distinct-ids/distinct-ids-chart.es.js.map +1 -1
- package/dist/domains/metrics/components/failure-rate-card.cjs.js +3 -2
- package/dist/domains/metrics/components/failure-rate-card.cjs.js.map +1 -1
- package/dist/domains/metrics/components/failure-rate-card.es.js +3 -2
- package/dist/domains/metrics/components/failure-rate-card.es.js.map +1 -1
- package/dist/domains/metrics/components/latency-card.cjs.js +3 -2
- package/dist/domains/metrics/components/latency-card.cjs.js.map +1 -1
- package/dist/domains/metrics/components/latency-card.es.js +3 -2
- package/dist/domains/metrics/components/latency-card.es.js.map +1 -1
- package/dist/domains/metrics/components/metrics-time-range-chip.cjs.js +1 -1
- package/dist/domains/metrics/components/metrics-time-range-chip.es.js +1 -1
- package/dist/domains/metrics/components/scores-card.cjs.js +2 -1
- package/dist/domains/metrics/components/scores-card.cjs.js.map +1 -1
- package/dist/domains/metrics/components/scores-card.es.js +2 -1
- package/dist/domains/metrics/components/scores-card.es.js.map +1 -1
- package/dist/domains/metrics/components/token-usage-card.cjs.js +3 -3
- package/dist/domains/metrics/components/token-usage-card.es.js +3 -3
- package/dist/domains/metrics/components/trace-aggregate-card.cjs.js +1 -1
- package/dist/domains/metrics/components/trace-aggregate-card.es.js +1 -1
- package/dist/domains/observability/components/observability-histogram.cjs.js +1 -1
- package/dist/domains/observability/components/observability-histogram.es.js +1 -1
- package/dist/domains/spans/components/spans-filter-bar.cjs.js +2 -2
- package/dist/domains/spans/components/spans-filter-bar.es.js +2 -2
- package/dist/domains/spans/hooks/use-spans-filter-bar.cjs.js +1 -1
- package/dist/domains/spans/hooks/use-spans-filter-bar.es.js +1 -1
- package/dist/domains/threads/components/threads-filter-bar.cjs.js +2 -2
- package/dist/domains/threads/components/threads-filter-bar.es.js +2 -2
- package/dist/domains/threads/hooks/use-threads-filter-bar.cjs.js +1 -1
- package/dist/domains/threads/hooks/use-threads-filter-bar.es.js +1 -1
- package/dist/domains/traces/components/traces-explorer/traces-explorer-filter-bar.cjs.js +2 -2
- package/dist/domains/traces/components/traces-explorer/traces-explorer-filter-bar.es.js +2 -2
- package/dist/domains/traces/components/traces-explorer/use-traces-filter-bar.cjs.js +1 -1
- package/dist/domains/traces/components/traces-explorer/use-traces-filter-bar.es.js +1 -1
- package/dist/{filter-bar-time-range-mg9JeYue.js → filter-bar-time-range-qlV8p7hp.js} +2 -2
- package/dist/{filter-bar-time-range-mg9JeYue.js.map → filter-bar-time-range-qlV8p7hp.js.map} +1 -1
- package/dist/{filter-bar-time-range-BA-pXMlo.cjs → filter-bar-time-range-zdM024JD.cjs} +2 -2
- package/dist/{filter-bar-time-range-BA-pXMlo.cjs.map → filter-bar-time-range-zdM024JD.cjs.map} +1 -1
- package/dist/src/domains/chat/tools/connect-prompt-tool.d.ts +10 -1
- package/dist/src/domains/connect/types.d.ts +16 -0
- package/dist/src/domains/dashboards/components/dashboard-renderer.d.ts +4 -2
- package/dist/src/domains/dashboards/hooks/use-widget-views.d.ts +4 -3
- package/dist/src/domains/dashboards/index.d.ts +1 -1
- package/dist/src/domains/dashboards/kinds/scores/scores-widget.d.ts +1 -1
- package/dist/src/domains/dashboards/lib/chrome.d.ts +6 -4
- package/dist/src/domains/dashboards/widget-kind.d.ts +12 -0
- package/dist/src/ds/components/DateTimeRangePicker/date-time-range-picker.d.ts +12 -1
- package/dist/src/ds/components/MetricsCard/metrics-card-headline.d.ts +1 -1
- package/dist/src/ds/components/MetricsLineChart/metrics-line-chart.d.ts +8 -0
- package/dist/style.css +4 -0
- package/dist/{tool-card-rxhTDpk_.cjs → tool-card-BOLXeKIp.cjs} +4 -1
- package/dist/{tool-card-rxhTDpk_.cjs.map → tool-card-BOLXeKIp.cjs.map} +1 -1
- package/dist/{tool-card-toDgoZzJ.js → tool-card-HZ5bm7ue.js} +4 -1
- package/dist/{tool-card-toDgoZzJ.js.map → tool-card-HZ5bm7ue.js.map} +1 -1
- package/package.json +9 -9
- package/dist/MetricsCard-CEB-f2sv.cjs.map +0 -1
- package/dist/MetricsCard-M8awJ2B_.js.map +0 -1
- package/dist/MetricsLineChart-BhHoduO0.js.map +0 -1
- package/dist/MetricsLineChart-CjBm4IXu.cjs.map +0 -1
- package/dist/date-time-range-picker-CTt7Od0P.cjs.map +0 -1
- package/dist/date-time-range-picker-DKh1K9Iy.js.map +0 -1
|
@@ -176,6 +176,16 @@ var DATE_PRESETS = [
|
|
|
176
176
|
label: "Custom range..."
|
|
177
177
|
}
|
|
178
178
|
];
|
|
179
|
+
var sameTime = (a, b) => a?.getTime() === b?.getTime();
|
|
180
|
+
/**
|
|
181
|
+
* Whether a named range breaks the limits the custom editor enforces: it starts before
|
|
182
|
+
* `minDate`'s day, or spans more than `maxRangeMs` (an open end counts up to now).
|
|
183
|
+
*/
|
|
184
|
+
function outsideLimits(range, minDate, maxRangeMs) {
|
|
185
|
+
const tooEarly = minDate !== void 0 && range.from < startOfDay(minDate);
|
|
186
|
+
const span = (range.to ?? /* @__PURE__ */ new Date()).getTime() - range.from.getTime();
|
|
187
|
+
return tooEarly || maxRangeMs !== void 0 && span > maxRangeMs;
|
|
188
|
+
}
|
|
179
189
|
var fmtClock = (d) => d.toLocaleTimeString("en-US", d.getMinutes() === 0 ? { hour: "numeric" } : {
|
|
180
190
|
hour: "numeric",
|
|
181
191
|
minute: "2-digit"
|
|
@@ -197,7 +207,7 @@ function customRangeLabel(dateFrom, dateTo) {
|
|
|
197
207
|
if (!dateTo) return `${start} \u2013 End`;
|
|
198
208
|
return `${start} \u2013 ${dateFrom !== void 0 && fmtDay(dateFrom) === fmtDay(dateTo) ? fmtClock(dateTo) : fmtMoment(dateTo)}`;
|
|
199
209
|
}
|
|
200
|
-
function DateTimeRangePicker({ preset = "all", onPresetChange, dateFrom, dateTo, onDateChange, onDateRangeChange, disabled, maxRangeMs, minDate, presets, size = "sm", defaultPreset, onReset, ref }) {
|
|
210
|
+
function DateTimeRangePicker({ preset = "all", onPresetChange, dateFrom, dateTo, onDateChange, onDateRangeChange, disabled, maxRangeMs, minDate, presets, namedRanges = [], size = "sm", defaultPreset, onReset, ref }) {
|
|
201
211
|
const visiblePresets = presets ? DATE_PRESETS.filter((p) => presets.includes(p.value)) : DATE_PRESETS;
|
|
202
212
|
const [customRangeOpen, setCustomRangeOpen] = useState(false);
|
|
203
213
|
const [presetMenuOpen, setPresetMenuOpen] = useState(false);
|
|
@@ -240,9 +250,16 @@ function DateTimeRangePicker({ preset = "all", onPresetChange, dateFrom, dateTo,
|
|
|
240
250
|
if (presetBeforeCustom) applyPreset(presetBeforeCustom);
|
|
241
251
|
setPresetMenuOpen(true);
|
|
242
252
|
};
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
const
|
|
253
|
+
const namedIndex = shownPreset === "custom" ? namedRanges.findIndex((r) => sameTime(r.from, dateFrom) && sameTime(r.to, dateTo)) : -1;
|
|
254
|
+
const named = namedRanges[namedIndex];
|
|
255
|
+
const canReset = defaultPreset !== void 0 && shownPreset === "custom" && !named;
|
|
256
|
+
const editingCustomRange = shownPreset === "custom" && !presetMenuOpen && (!named || customRangeOpen);
|
|
257
|
+
const label = named ? named.label : shownPreset === "custom" ? customRangeLabel(dateFrom, dateTo) : datePresetLabel;
|
|
258
|
+
const applyNamedRange = (range) => {
|
|
259
|
+
setPresetMenuOpen(false);
|
|
260
|
+
onPresetChange?.("custom");
|
|
261
|
+
applyCustomRange(range.from, range.to);
|
|
262
|
+
};
|
|
246
263
|
const trigger = /* @__PURE__ */ jsx(Button, {
|
|
247
264
|
ref,
|
|
248
265
|
size,
|
|
@@ -279,14 +296,24 @@ function DateTimeRangePicker({ preset = "all", onPresetChange, dateFrom, dateTo,
|
|
|
279
296
|
onOpenChange: setPresetMenuOpen,
|
|
280
297
|
children: [/* @__PURE__ */ jsx(DropdownMenu.Trigger, { render: trigger }), /* @__PURE__ */ jsx(DropdownMenu.Content, {
|
|
281
298
|
align: "start",
|
|
282
|
-
children: /* @__PURE__ */
|
|
283
|
-
value: shownPreset,
|
|
284
|
-
children:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
299
|
+
children: /* @__PURE__ */ jsxs(DropdownMenu.RadioGroup, {
|
|
300
|
+
value: named ? `named-${namedIndex}` : shownPreset,
|
|
301
|
+
children: [
|
|
302
|
+
namedRanges.map((r, i) => /* @__PURE__ */ jsx(DropdownMenu.RadioItem, {
|
|
303
|
+
value: `named-${i}`,
|
|
304
|
+
disabled: outsideLimits(r, minDate, maxRangeMs),
|
|
305
|
+
closeOnClick: true,
|
|
306
|
+
onClick: () => applyNamedRange(r),
|
|
307
|
+
children: r.label
|
|
308
|
+
}, `named-${i}`)),
|
|
309
|
+
namedRanges.length > 0 && /* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
|
|
310
|
+
visiblePresets.map((p) => /* @__PURE__ */ jsx(DropdownMenu.RadioItem, {
|
|
311
|
+
value: p.value,
|
|
312
|
+
closeOnClick: true,
|
|
313
|
+
onClick: () => handlePresetSelect(p.value),
|
|
314
|
+
children: p.label
|
|
315
|
+
}, p.value))
|
|
316
|
+
]
|
|
290
317
|
})
|
|
291
318
|
})]
|
|
292
319
|
})]
|
|
@@ -313,4 +340,4 @@ function JoinReset({ reset, size, children }) {
|
|
|
313
340
|
}
|
|
314
341
|
export { DateTimeRangePicker as t };
|
|
315
342
|
|
|
316
|
-
//# sourceMappingURL=date-time-range-picker-
|
|
343
|
+
//# sourceMappingURL=date-time-range-picker-CdSiXwB4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-time-range-picker-CdSiXwB4.js","names":["_Fragment"],"sources":["../src/ds/components/DateTimeRangePicker/custom-range-editor.tsx","../src/ds/components/DateTimeRangePicker/date-time-range-picker.tsx"],"sourcesContent":["import { format, isValid, parse, startOfDay } from 'date-fns';\nimport { Check } from 'lucide-react';\nimport { useId, useState } from 'react';\nimport { Button } from '@/ds/components/Button/Button';\nimport { DatePicker, TimePicker } from '@/ds/components/DateTimePicker';\nimport { Field, FieldError } from '@/ds/components/Field';\nimport { Txt } from '@/ds/components/Txt';\nimport { cn } from '@/lib/utils';\n\nfunction formatDays(ms: number): string {\n const days = Math.round(ms / (24 * 60 * 60 * 1000));\n return `${days} day${days === 1 ? '' : 's'}`;\n}\n\n/** The draft day at the draft time, or the bare day when the time doesn't parse. */\nfunction combineDateAndTime(date: Date | undefined, timeStr: string): Date | undefined {\n if (!date) return undefined;\n const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());\n const combined = parse(timeStr, 'h:mm a', dateOnly);\n return isValid(combined) ? combined : date;\n}\n\nexport interface CustomRangeEditorProps {\n dateFrom?: Date;\n dateTo?: Date;\n disabled?: boolean;\n /** Widest range that can be applied, in milliseconds. */\n maxRangeMs?: number;\n /** Earliest day a range can start on. Days before it are disabled. */\n minDate?: Date;\n onApply: (from: Date | undefined, to: Date | undefined) => void;\n onShowPresets: () => void;\n}\n\n/**\n * The custom range popover body. It mounts each time the popover opens, so its drafts\n * start from the current range without the picker resetting them.\n */\nexport function CustomRangeEditor({\n dateFrom,\n dateTo,\n disabled,\n maxRangeMs,\n minDate,\n onApply,\n onShowPresets,\n}: CustomRangeEditorProps) {\n const [draftDateFrom, setDraftDateFrom] = useState<Date | undefined>(dateFrom);\n const [draftDateTo, setDraftDateTo] = useState<Date | undefined>(dateTo);\n const [draftTimeFrom, setDraftTimeFrom] = useState('12:00 AM');\n const [draftTimeTo, setDraftTimeTo] = useState('11:59 PM');\n const [applyAttempted, setApplyAttempted] = useState(false);\n const errorId = useId();\n\n const fromDate = combineDateAndTime(draftDateFrom, draftTimeFrom);\n const toDate = combineDateAndTime(draftDateTo, draftTimeTo);\n const rangeReversed = fromDate !== undefined && toDate !== undefined && fromDate.getTime() > toDate.getTime();\n const rangeTooWide =\n fromDate !== undefined && toDate !== undefined && toDate.getTime() - fromDate.getTime() > (maxRangeMs ?? Infinity);\n const firstDay = minDate ? startOfDay(minDate) : undefined;\n const endFirstDay = draftDateFrom && (!firstDay || draftDateFrom > firstDay) ? draftDateFrom : firstDay;\n const rangeTooEarly = fromDate !== undefined && firstDay !== undefined && fromDate < firstDay;\n const rangeInvalid = rangeReversed || rangeTooWide || rangeTooEarly;\n // Shown after a failed Apply, and gone as soon as the drafts form a valid range again.\n let error: string | undefined;\n if (applyAttempted && rangeReversed) error = 'Start date/time must be before end date/time';\n else if (applyAttempted && rangeTooWide) error = `Range cannot be longer than ${formatDays(maxRangeMs ?? 0)}`;\n else if (applyAttempted && firstDay && rangeTooEarly)\n error = `Range cannot start before ${format(firstDay, 'MMM d, yyyy')}`;\n\n const apply = () => {\n if (rangeInvalid) {\n setApplyAttempted(true);\n return;\n }\n onApply(fromDate, toDate);\n };\n\n return (\n <>\n <div\n role=\"group\"\n aria-label=\"Custom date range\"\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n className=\"flex\"\n >\n <div className={cn('border-r border-border')}>\n <Txt as=\"span\" variant=\"column\" tone=\"muted\" className=\"block px-4 pt-3\">\n Start\n </Txt>\n <DatePicker\n mode=\"single\"\n selected={draftDateFrom}\n month={draftDateFrom}\n onSelect={setDraftDateFrom}\n disabled={disabled}\n fromDate={firstDay}\n toDate={draftDateTo}\n />\n <TimePicker\n label=\"Start time\"\n className=\"mx-4 mb-3 w-auto\"\n defaultValue={draftTimeFrom}\n onValueChange={v => {\n if (!disabled) setDraftTimeFrom(v);\n }}\n />\n </div>\n <div>\n <Txt as=\"span\" variant=\"column\" tone=\"muted\" className=\"block px-4 pt-3\">\n End\n </Txt>\n <DatePicker\n mode=\"single\"\n selected={draftDateTo}\n month={draftDateTo}\n onSelect={setDraftDateTo}\n disabled={disabled}\n fromDate={endFirstDay}\n />\n <TimePicker\n label=\"End time\"\n className=\"mx-4 mb-3 w-auto\"\n defaultValue={draftTimeTo}\n onValueChange={v => {\n if (!disabled) setDraftTimeTo(v);\n }}\n />\n </div>\n </div>\n <Field invalid={Boolean(error)}>\n <FieldError id={errorId} className=\"px-4 pb-1\">\n {error}\n </FieldError>\n </Field>\n <div className={cn('flex items-center justify-between px-4 pb-3')}>\n <Button variant=\"ghost\" size=\"sm\" disabled={disabled} onClick={onShowPresets}>\n ← Presets\n </Button>\n <Button icon={<Check />} variant=\"primary\" size=\"sm\" onClick={apply} disabled={disabled}>\n Apply\n </Button>\n </div>\n </>\n );\n}\n","import { startOfDay } from 'date-fns';\nimport { CalendarIcon, XIcon } from 'lucide-react';\nimport { useState } from 'react';\nimport type { ReactNode, Ref } from 'react';\nimport { CustomRangeEditor } from './custom-range-editor';\nimport { Button, TriggerChevron } from '@/ds/components/Button/Button';\nimport type { TextButtonSize } from '@/ds/components/Button/Button';\nimport { ButtonsGroup } from '@/ds/components/ButtonsGroup/buttons-group';\nimport { DropdownMenu } from '@/ds/components/DropdownMenu/dropdown-menu';\nimport { Popover, PopoverTrigger, PopoverContent } from '@/ds/components/Popover/popover';\nimport { cn } from '@/lib/utils';\n\nexport type DateRangePreset = 'all' | 'last-24h' | 'last-3d' | 'last-7d' | 'last-14d' | 'last-30d' | 'custom';\n\nconst DATE_PRESETS: { value: DateRangePreset; label: string; ms?: number }[] = [\n { value: 'all', label: 'All' },\n { value: 'last-24h', label: 'Last 24 hours', ms: 24 * 60 * 60 * 1000 },\n { value: 'last-3d', label: 'Last 3 days', ms: 3 * 24 * 60 * 60 * 1000 },\n { value: 'last-7d', label: 'Last 7 days', ms: 7 * 24 * 60 * 60 * 1000 },\n { value: 'last-14d', label: 'Last 14 days', ms: 14 * 24 * 60 * 60 * 1000 },\n { value: 'last-30d', label: 'Last 30 days', ms: 30 * 24 * 60 * 60 * 1000 },\n { value: 'custom', label: 'Custom range...' },\n];\n\nconst sameTime = (a: Date | undefined, b: Date | undefined) => a?.getTime() === b?.getTime();\n\n/**\n * Whether a named range breaks the limits the custom editor enforces: it starts before\n * `minDate`'s day, or spans more than `maxRangeMs` (an open end counts up to now).\n */\nfunction outsideLimits(range: DateTimeRangePickerNamedRange, minDate?: Date, maxRangeMs?: number) {\n const tooEarly = minDate !== undefined && range.from < startOfDay(minDate);\n const span = (range.to ?? new Date()).getTime() - range.from.getTime();\n const tooWide = maxRangeMs !== undefined && span > maxRangeMs;\n return tooEarly || tooWide;\n}\n\nconst fmtClock = (d: Date) =>\n d.toLocaleTimeString('en-US', d.getMinutes() === 0 ? { hour: 'numeric' } : { hour: 'numeric', minute: '2-digit' });\n\n/**\n * A custom range as day and time, kept short: `Oct 21, 2:05 PM – 4 PM` within one day,\n * `Oct 20, 12 AM – Oct 27, 9:30 AM` across days, with years only outside the current one.\n */\nfunction customRangeLabel(dateFrom: Date | undefined, dateTo: Date | undefined) {\n const thisYear = new Date().getFullYear();\n const withYear = [dateFrom, dateTo].some(d => d && d.getFullYear() !== thisYear);\n const fmtDay = (d: Date) =>\n d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', ...(withYear && { year: 'numeric' }) });\n const fmtMoment = (d: Date) => `${fmtDay(d)}, ${fmtClock(d)}`;\n\n const start = dateFrom ? fmtMoment(dateFrom) : 'Start';\n if (!dateTo) return `${start} \\u2013 End`;\n const sameDay = dateFrom !== undefined && fmtDay(dateFrom) === fmtDay(dateTo);\n return `${start} \\u2013 ${sameDay ? fmtClock(dateTo) : fmtMoment(dateTo)}`;\n}\n\n/** A fixed range offered beside the presets, e.g. the current billing period. */\nexport type DateTimeRangePickerNamedRange = { label: string; from: Date; to?: Date };\n\nexport interface DateTimeRangePickerProps {\n preset?: DateRangePreset;\n onPresetChange?: (preset: DateRangePreset) => void;\n dateFrom?: Date;\n dateTo?: Date;\n onDateChange?: (value: Date | undefined, type: 'from' | 'to') => void;\n /**\n * Called once with both ends when a custom range is applied. When provided it replaces\n * the two `onDateChange` calls for that step, so consumers can write the range atomically.\n */\n onDateRangeChange?: (from: Date | undefined, to: Date | undefined) => void;\n disabled?: boolean;\n /** Widest custom range that can be applied, in milliseconds. Presets are unaffected. */\n maxRangeMs?: number;\n /** Earliest day a custom range can start on, e.g. the oldest data still kept. Earlier days are disabled. */\n minDate?: Date;\n /** Subset of presets to show. If omitted, all presets are shown. */\n presets?: readonly DateRangePreset[];\n /**\n * Fixed ranges listed above the presets, e.g. `{ label: 'This period', from: periodStart }`.\n * Picking one applies it as a custom range; while it's applied the trigger shows its label.\n */\n namedRanges?: readonly DateTimeRangePickerNamedRange[];\n /** Size of the trigger Button. Defaults to 'sm', the toolbar rung. */\n size?: TextButtonSize;\n /**\n * The preset a custom range resets to. While a custom range is applied, a × joined to the\n * trigger takes the chevron's place and switches back to it.\n */\n defaultPreset?: DateRangePreset;\n /** Replaces what the reset × does, e.g. to undo a histogram zoom before falling back to `defaultPreset`. */\n onReset?: () => void;\n /** The trigger Button. */\n ref?: Ref<HTMLButtonElement>;\n}\n\nexport function DateTimeRangePicker({\n preset = 'all',\n onPresetChange,\n dateFrom,\n dateTo,\n onDateChange,\n onDateRangeChange,\n disabled,\n maxRangeMs,\n minDate,\n presets,\n namedRanges = [],\n size = 'sm',\n defaultPreset,\n onReset,\n ref,\n}: DateTimeRangePickerProps) {\n const visiblePresets = presets ? DATE_PRESETS.filter(p => presets.includes(p.value)) : DATE_PRESETS;\n const [customRangeOpen, setCustomRangeOpen] = useState(false);\n const [presetMenuOpen, setPresetMenuOpen] = useState(false);\n // The preset active before \"Custom range...\" was picked, until a range is applied.\n // \"← Presets\" restores it instead of jumping to the first preset in the list.\n const [presetBeforeCustom, setPresetBeforeCustom] = useState<DateRangePreset | undefined>();\n\n // \"← Presets\" restores the previous preset through the parent, which may apply it a\n // render later (e.g. a router URL update). Show it right away instead of \"custom\".\n const restoringPreset = presetMenuOpen && preset === 'custom' ? presetBeforeCustom : undefined;\n const shownPreset = restoringPreset ?? preset;\n const datePresetLabel = DATE_PRESETS.find(p => p.value === shownPreset)?.label ?? 'All';\n\n const applyPreset = (value: DateRangePreset) => {\n onPresetChange?.(value);\n const entry = DATE_PRESETS.find(p => p.value === value);\n if (entry?.ms) {\n onDateChange?.(new Date(Date.now() - entry.ms), 'from');\n onDateChange?.(undefined, 'to');\n } else {\n onDateChange?.(undefined, 'from');\n onDateChange?.(undefined, 'to');\n }\n };\n\n const handlePresetSelect = (value: DateRangePreset) => {\n setPresetMenuOpen(false);\n if (value === 'custom') {\n if (preset !== 'custom') setPresetBeforeCustom(preset);\n onPresetChange?.(value);\n setCustomRangeOpen(true);\n return;\n }\n setPresetBeforeCustom(undefined);\n applyPreset(value);\n };\n\n const applyCustomRange = (fromDate: Date | undefined, toDate: Date | undefined) => {\n if (onDateRangeChange) {\n onDateRangeChange(fromDate, toDate);\n } else {\n onDateChange?.(fromDate, 'from');\n onDateChange?.(toDate, 'to');\n }\n setPresetBeforeCustom(undefined);\n setCustomRangeOpen(false);\n };\n\n // Back to the preset list. Restores the preset the user came from; an applied custom\n // range matches no preset, so it stays and the list shows \"Custom range...\" as current.\n const showPresets = () => {\n setCustomRangeOpen(false);\n if (presetBeforeCustom) applyPreset(presetBeforeCustom);\n setPresetMenuOpen(true);\n };\n\n // The applied custom range, when it is one of the named ranges.\n const namedIndex =\n shownPreset === 'custom' ? namedRanges.findIndex(r => sameTime(r.from, dateFrom) && sameTime(r.to, dateTo)) : -1;\n const named = namedRanges[namedIndex];\n const canReset = defaultPreset !== undefined && shownPreset === 'custom' && !named;\n // A named range shows the preset list, unless \"Custom range...\" was picked to edit it.\n const editingCustomRange = shownPreset === 'custom' && !presetMenuOpen && (!named || customRangeOpen);\n const label = named ? named.label : shownPreset === 'custom' ? customRangeLabel(dateFrom, dateTo) : datePresetLabel;\n\n const applyNamedRange = (range: DateTimeRangePickerNamedRange) => {\n setPresetMenuOpen(false);\n onPresetChange?.('custom');\n applyCustomRange(range.from, range.to);\n };\n const trigger = (\n <Button\n ref={ref}\n size={size}\n disabled={disabled}\n icon={<CalendarIcon />}\n iconEnd={canReset ? undefined : <TriggerChevron />}\n >\n {label}\n </Button>\n );\n\n return (\n <JoinReset\n size={size}\n reset={\n canReset && (\n <ResetRangeButton size={size} disabled={disabled} onReset={onReset ?? (() => applyPreset(defaultPreset))} />\n )\n }\n >\n {editingCustomRange && (\n <Popover open={customRangeOpen} onOpenChange={setCustomRangeOpen}>\n <PopoverTrigger render={trigger} />\n <PopoverContent align=\"start\" className={cn('w-auto p-0')}>\n <CustomRangeEditor\n dateFrom={dateFrom}\n dateTo={dateTo}\n disabled={disabled}\n maxRangeMs={maxRangeMs}\n minDate={minDate}\n onApply={applyCustomRange}\n onShowPresets={showPresets}\n />\n </PopoverContent>\n </Popover>\n )}\n {!editingCustomRange && (\n <DropdownMenu open={presetMenuOpen} onOpenChange={setPresetMenuOpen}>\n <DropdownMenu.Trigger render={trigger} />\n <DropdownMenu.Content align=\"start\">\n {/* Items select on click (not onValueChange) so re-picking the checked\n \"Custom range...\" still reopens the range editor. */}\n <DropdownMenu.RadioGroup value={named ? `named-${namedIndex}` : shownPreset}>\n {namedRanges.map((r, i) => (\n <DropdownMenu.RadioItem\n key={`named-${i}`}\n value={`named-${i}`}\n disabled={outsideLimits(r, minDate, maxRangeMs)}\n closeOnClick\n onClick={() => applyNamedRange(r)}\n >\n {r.label}\n </DropdownMenu.RadioItem>\n ))}\n {namedRanges.length > 0 && <DropdownMenu.Separator />}\n {visiblePresets.map(p => (\n <DropdownMenu.RadioItem\n key={p.value}\n value={p.value}\n closeOnClick\n onClick={() => handlePresetSelect(p.value)}\n >\n {p.label}\n </DropdownMenu.RadioItem>\n ))}\n </DropdownMenu.RadioGroup>\n </DropdownMenu.Content>\n </DropdownMenu>\n )}\n </JoinReset>\n );\n}\n\n/** Switches a custom range back to the picker's default preset. */\nfunction ResetRangeButton({\n size,\n disabled,\n onReset,\n}: {\n size: TextButtonSize;\n disabled?: boolean;\n onReset: () => void;\n}) {\n return (\n <Button\n type=\"button\"\n size={`icon-${size}` as const}\n tooltip=\"Reset time range\"\n disabled={disabled}\n onClick={onReset}\n >\n <XIcon />\n </Button>\n );\n}\n\n/** The trigger alone, or joined with the reset × as one pill. */\nfunction JoinReset({ reset, size, children }: { reset: ReactNode; size: TextButtonSize; children: ReactNode }) {\n if (!reset) return children;\n return (\n <ButtonsGroup size={size}>\n {children}\n {reset}\n </ButtonsGroup>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AASA,SAAS,WAAW,IAAoB;CACtC,MAAM,OAAO,KAAK,MAAM,KAAM,KAAoB;CAClD,OAAO,GAAG,KAAK,MAAM,SAAS,IAAI,KAAK;AACzC;;AAGA,SAAS,mBAAmB,MAAwB,SAAmC;CACrF,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,MAAM,WAAW,IAAI,KAAK,KAAK,YAAY,GAAG,KAAK,SAAS,GAAG,KAAK,QAAQ,CAAC;CAC7E,MAAM,WAAW,MAAM,SAAS,UAAU,QAAQ;CAClD,OAAO,QAAQ,QAAQ,IAAI,WAAW;AACxC;;;;;AAkBA,SAAgB,kBAAkB,EAChC,UACA,QACA,UACA,YACA,SACA,SACA,iBACyB;CACzB,MAAM,CAAC,eAAe,oBAAoB,SAA2B,QAAQ;CAC7E,MAAM,CAAC,aAAa,kBAAkB,SAA2B,MAAM;CACvE,MAAM,CAAC,eAAe,oBAAoB,SAAS,UAAU;CAC7D,MAAM,CAAC,aAAa,kBAAkB,SAAS,UAAU;CACzD,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,KAAK;CAC1D,MAAM,UAAU,MAAM;CAEtB,MAAM,WAAW,mBAAmB,eAAe,aAAa;CAChE,MAAM,SAAS,mBAAmB,aAAa,WAAW;CAC1D,MAAM,gBAAgB,aAAa,KAAA,KAAa,WAAW,KAAA,KAAa,SAAS,QAAQ,IAAI,OAAO,QAAQ;CAC5G,MAAM,eACJ,aAAa,KAAA,KAAa,WAAW,KAAA,KAAa,OAAO,QAAQ,IAAI,SAAS,QAAQ,KAAK,cAAc;CAC3G,MAAM,WAAW,UAAU,WAAW,OAAO,IAAI,KAAA;CACjD,MAAM,cAAc,kBAAkB,CAAC,YAAY,gBAAgB,YAAY,gBAAgB;CAC/F,MAAM,gBAAgB,aAAa,KAAA,KAAa,aAAa,KAAA,KAAa,WAAW;CACrF,MAAM,eAAe,iBAAiB,gBAAgB;CAEtD,IAAI;CACJ,IAAI,kBAAkB,eAAe,QAAQ;MACxC,IAAI,kBAAkB,cAAc,QAAQ,+BAA+B,WAAW,cAAc,CAAC;MACrG,IAAI,kBAAkB,YAAY,eACrC,QAAQ,6BAA6B,OAAO,UAAU,aAAa;CAErE,MAAM,cAAc;EAClB,IAAI,cAAc;GAChB,kBAAkB,IAAI;GACtB;EACF;EACA,QAAQ,UAAU,MAAM;CAC1B;CAEA,OACE,qBAAAA,YAAA,EAAA,UAAA;EACE,qBAAC,OAAD;GACE,MAAK;GACL,cAAW;GACX,gBAAc,QAAQ,OAAO,KAAA;GAC7B,oBAAkB,QAAQ,UAAU,KAAA;GACpC,WAAU;GALZ,UAAA,CAOE,qBAAC,OAAD;IAAK,WAAW,GAAG,wBAAwB;IAA3C,UAAA;KACE,oBAAC,KAAD;MAAK,IAAG;MAAO,SAAQ;MAAS,MAAK;MAAQ,WAAU;MAAkB,UAAA;KAEpE,CAAA;KACL,oBAAC,YAAD;MACE,MAAK;MACL,UAAU;MACV,OAAO;MACP,UAAU;MACA;MACV,UAAU;MACV,QAAQ;KACT,CAAA;KACD,oBAAC,YAAD;MACE,OAAM;MACN,WAAU;MACV,cAAc;MACd,gBAAe,MAAK;OAClB,IAAI,CAAC,UAAU,iBAAiB,CAAC;MACnC;KACD,CAAA;IACE;GACL,CAAA,GAAA,qBAAC,OAAD,EAAA,UAAA;IACE,oBAAC,KAAD;KAAK,IAAG;KAAO,SAAQ;KAAS,MAAK;KAAQ,WAAU;KAAkB,UAAA;IAEpE,CAAA;IACL,oBAAC,YAAD;KACE,MAAK;KACL,UAAU;KACV,OAAO;KACP,UAAU;KACA;KACV,UAAU;IACX,CAAA;IACD,oBAAC,YAAD;KACE,OAAM;KACN,WAAU;KACV,cAAc;KACd,gBAAe,MAAK;MAClB,IAAI,CAAC,UAAU,eAAe,CAAC;KACjC;IACD,CAAA;GACE,EAAA,CAAA,CACF;;EACL,oBAAC,OAAD;GAAO,SAAS,QAAQ,KAAK;GAC3B,UAAA,oBAAC,YAAD;IAAY,IAAI;IAAS,WAAU;IAChC,UAAA;GACS,CAAA;EACP,CAAA;EACP,qBAAC,OAAD;GAAK,WAAW,GAAG,6CAA6C;GAAhE,UAAA,CACE,oBAAC,QAAD;IAAQ,SAAQ;IAAQ,MAAK;IAAe;IAAU,SAAS;IAAe,UAAA;GAEtE,CAAA,GACR,oBAAC,QAAD;IAAQ,MAAM,oBAAC,OAAD,CAAQ,CAAA;IAAG,SAAQ;IAAU,MAAK;IAAK,SAAS;IAAiB;IAAU,UAAA;GAEjF,CAAA,CACL;;CACL,EAAA,CAAA;AAEN;;ACpIA,IAAM,eAAyE;CAC7E;EAAE,OAAO;EAAO,OAAO;CAAM;CAC7B;EAAE,OAAO;EAAY,OAAO;EAAiB,IAAI;CAAoB;CACrE;EAAE,OAAO;EAAW,OAAO;EAAe,IAAI;CAAwB;CACtE;EAAE,OAAO;EAAW,OAAO;EAAe,IAAI;CAAwB;CACtE;EAAE,OAAO;EAAY,OAAO;EAAgB,IAAI;CAAyB;CACzE;EAAE,OAAO;EAAY,OAAO;EAAgB,IAAI;CAAyB;CACzE;EAAE,OAAO;EAAU,OAAO;CAAkB;AAC9C;AAEA,IAAM,YAAY,GAAqB,MAAwB,GAAG,QAAQ,MAAM,GAAG,QAAQ;;;;;AAM3F,SAAS,cAAc,OAAsC,SAAgB,YAAqB;CAChG,MAAM,WAAW,YAAY,KAAA,KAAa,MAAM,OAAO,WAAW,OAAO;CACzE,MAAM,QAAQ,MAAM,sBAAM,IAAI,KAAK,EAAA,CAAG,QAAQ,IAAI,MAAM,KAAK,QAAQ;CAErE,OAAO,YADS,eAAe,KAAA,KAAa,OAAO;AAErD;AAEA,IAAM,YAAY,MAChB,EAAE,mBAAmB,SAAS,EAAE,WAAW,MAAM,IAAI,EAAE,MAAM,UAAU,IAAI;CAAE,MAAM;CAAW,QAAQ;AAAU,CAAC;;;;;AAMnH,SAAS,iBAAiB,UAA4B,QAA0B;CAC9E,MAAM,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;CACxC,MAAM,WAAW,CAAC,UAAU,MAAM,CAAC,CAAC,MAAK,MAAK,KAAK,EAAE,YAAY,MAAM,QAAQ;CAC/E,MAAM,UAAU,MACd,EAAE,mBAAmB,SAAS;EAAE,OAAO;EAAS,KAAK;EAAW,GAAI,YAAY,EAAE,MAAM,UAAU;CAAG,CAAC;CACxG,MAAM,aAAa,MAAY,GAAG,OAAO,CAAC,EAAE,IAAI,SAAS,CAAC;CAE1D,MAAM,QAAQ,WAAW,UAAU,QAAQ,IAAI;CAC/C,IAAI,CAAC,QAAQ,OAAO,GAAG,MAAM;CAE7B,OAAO,GAAG,MAAM,UADA,aAAa,KAAA,KAAa,OAAO,QAAQ,MAAM,OAAO,MAAM,IACxC,SAAS,MAAM,IAAI,UAAU,MAAM;AACzE;AAyCA,SAAgB,oBAAoB,EAClC,SAAS,OACT,gBACA,UACA,QACA,cACA,mBACA,UACA,YACA,SACA,SACA,cAAc,CAAC,GACf,OAAO,MACP,eACA,SACA,OAC2B;CAC3B,MAAM,iBAAiB,UAAU,aAAa,QAAO,MAAK,QAAQ,SAAS,EAAE,KAAK,CAAC,IAAI;CACvF,MAAM,CAAC,iBAAiB,sBAAsB,SAAS,KAAK;CAC5D,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,KAAK;CAG1D,MAAM,CAAC,oBAAoB,yBAAyB,SAAsC;CAK1F,MAAM,eADkB,kBAAkB,WAAW,WAAW,qBAAqB,KAAA,MAC9C;CACvC,MAAM,kBAAkB,aAAa,MAAK,MAAK,EAAE,UAAU,WAAW,CAAC,EAAE,SAAS;CAElF,MAAM,eAAe,UAA2B;EAC9C,iBAAiB,KAAK;EACtB,MAAM,QAAQ,aAAa,MAAK,MAAK,EAAE,UAAU,KAAK;EACtD,IAAI,OAAO,IAAI;GACb,eAAe,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,EAAE,GAAG,MAAM;GACtD,eAAe,KAAA,GAAW,IAAI;EAChC,OAAO;GACL,eAAe,KAAA,GAAW,MAAM;GAChC,eAAe,KAAA,GAAW,IAAI;EAChC;CACF;CAEA,MAAM,sBAAsB,UAA2B;EACrD,kBAAkB,KAAK;EACvB,IAAI,UAAU,UAAU;GACtB,IAAI,WAAW,UAAU,sBAAsB,MAAM;GACrD,iBAAiB,KAAK;GACtB,mBAAmB,IAAI;GACvB;EACF;EACA,sBAAsB,KAAA,CAAS;EAC/B,YAAY,KAAK;CACnB;CAEA,MAAM,oBAAoB,UAA4B,WAA6B;EACjF,IAAI,mBACF,kBAAkB,UAAU,MAAM;OAC7B;GACL,eAAe,UAAU,MAAM;GAC/B,eAAe,QAAQ,IAAI;EAC7B;EACA,sBAAsB,KAAA,CAAS;EAC/B,mBAAmB,KAAK;CAC1B;CAIA,MAAM,oBAAoB;EACxB,mBAAmB,KAAK;EACxB,IAAI,oBAAoB,YAAY,kBAAkB;EACtD,kBAAkB,IAAI;CACxB;CAGA,MAAM,aACJ,gBAAgB,WAAW,YAAY,WAAU,MAAK,SAAS,EAAE,MAAM,QAAQ,KAAK,SAAS,EAAE,IAAI,MAAM,CAAC,IAAI;CAChH,MAAM,QAAQ,YAAY;CAC1B,MAAM,WAAW,kBAAkB,KAAA,KAAa,gBAAgB,YAAY,CAAC;CAE7E,MAAM,qBAAqB,gBAAgB,YAAY,CAAC,mBAAmB,CAAC,SAAS;CACrF,MAAM,QAAQ,QAAQ,MAAM,QAAQ,gBAAgB,WAAW,iBAAiB,UAAU,MAAM,IAAI;CAEpG,MAAM,mBAAmB,UAAyC;EAChE,kBAAkB,KAAK;EACvB,iBAAiB,QAAQ;EACzB,iBAAiB,MAAM,MAAM,MAAM,EAAE;CACvC;CACA,MAAM,UACJ,oBAAC,QAAD;EACO;EACC;EACI;EACV,MAAM,oBAAC,cAAD,CAAe,CAAA;EACrB,SAAS,WAAW,KAAA,IAAY,oBAAC,gBAAD,CAAiB,CAAA;EAEhD,UAAA;CACK,CAAA;CAGV,OACE,qBAAC,WAAD;EACQ;EACN,OACE,YACE,oBAAC,kBAAD;GAAwB;GAAgB;GAAU,SAAS,kBAAkB,YAAY,aAAa;EAAK,CAAA;EAJjH,UAAA,CAQG,sBACC,qBAAC,SAAD;GAAS,MAAM;GAAiB,cAAc;GAA9C,UAAA,CACE,oBAAC,gBAAD,EAAgB,QAAQ,QAAU,CAAA,GAClC,oBAAC,gBAAD;IAAgB,OAAM;IAAQ,WAAW,GAAG,YAAY;IACtD,UAAA,oBAAC,mBAAD;KACY;KACF;KACE;KACE;KACH;KACT,SAAS;KACT,eAAe;IAChB,CAAA;GACa,CAAA,CACT;EAEV,CAAA,GAAA,CAAC,sBACA,qBAAC,cAAD;GAAc,MAAM;GAAgB,cAAc;GAAlD,UAAA,CACE,oBAAC,aAAa,SAAd,EAAsB,QAAQ,QAAU,CAAA,GACxC,oBAAC,aAAa,SAAd;IAAsB,OAAM;IAG1B,UAAA,qBAAC,aAAa,YAAd;KAAyB,OAAO,QAAQ,SAAS,eAAe;KAAhE,UAAA;MACG,YAAY,KAAK,GAAG,MACnB,oBAAC,aAAa,WAAd;OAEE,OAAO,SAAS;OAChB,UAAU,cAAc,GAAG,SAAS,UAAU;OAC9C,cAAA;OACA,eAAe,gBAAgB,CAAC;OAE/B,UAAA,EAAE;MACmB,GAPjB,SAAS,GAOQ,CACzB;MACA,YAAY,SAAS,KAAK,oBAAC,aAAa,WAAd,CAAyB,CAAA;MACnD,eAAe,KAAI,MAClB,oBAAC,aAAa,WAAd;OAEE,OAAO,EAAE;OACT,cAAA;OACA,eAAe,mBAAmB,EAAE,KAAK;OAExC,UAAA,EAAE;MACmB,GANjB,EAAE,KAMe,CACzB;KACsB;;GACL,CAAA,CACV;EAEP,CAAA,CAAA;;AAEf;;AAGA,SAAS,iBAAiB,EACxB,MACA,UACA,WAKC;CACD,OACE,oBAAC,QAAD;EACE,MAAK;EACL,MAAM,QAAQ;EACd,SAAQ;EACE;EACV,SAAS;EAET,UAAA,oBAAC,OAAD,CAAQ,CAAA;CACF,CAAA;AAEZ;;AAGA,SAAS,UAAU,EAAE,OAAO,MAAM,YAA6E;CAC7G,IAAI,CAAC,OAAO,OAAO;CACnB,OACE,qBAAC,cAAD;EAAoB;EAApB,UAAA,CACG,UACA,KACW;;AAElB"}
|
|
@@ -22,7 +22,7 @@ const require_domains_chat_messages_renderers_reasoning_part_renderer = require(
|
|
|
22
22
|
const require_domains_chat_messages_renderers_status_renderers = require("./renderers/status-renderers.cjs.js");
|
|
23
23
|
const require_domains_chat_tools_tool_card_kind = require("../tools/tool-card-kind.cjs.js");
|
|
24
24
|
const require_domains_chat_tools_tool_call_effects = require("../tools/tool-call-effects.cjs.js");
|
|
25
|
-
const require_tool_card = require("../../../tool-card-
|
|
25
|
+
const require_tool_card = require("../../../tool-card-BOLXeKIp.cjs");
|
|
26
26
|
const require_domains_chat_tools_tool_groups = require("../tools/tool-groups.cjs.js");
|
|
27
27
|
const require_components_ai_message_reveal = require("../../../components/ai/message-reveal.cjs.js");
|
|
28
28
|
let react = require("react");
|
|
@@ -21,7 +21,7 @@ import { ReasoningPartRenderer } from "./renderers/reasoning-part-renderer.es.js
|
|
|
21
21
|
import { messageStatusRenderers } from "./renderers/status-renderers.es.js";
|
|
22
22
|
import { badgeStatus, isSettledState } from "../tools/tool-card-kind.es.js";
|
|
23
23
|
import { ToolCallEffects } from "../tools/tool-call-effects.es.js";
|
|
24
|
-
import { t as ToolCard } from "../../../tool-card-
|
|
24
|
+
import { t as ToolCard } from "../../../tool-card-HZ5bm7ue.js";
|
|
25
25
|
import { collectToolGroups } from "../tools/tool-groups.es.js";
|
|
26
26
|
import { useRevealedParts } from "../../../components/ai/message-reveal.es.js";
|
|
27
27
|
import { memo, useMemo } from "react";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_tool_card = require("../../../../tool-card-
|
|
2
|
+
const require_tool_card = require("../../../../tool-card-BOLXeKIp.cjs");
|
|
3
3
|
exports.AgentBadgeWrapper = require_tool_card.AgentBadgeWrapper;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { r as AgentBadgeWrapper } from "../../../../tool-card-
|
|
1
|
+
export { r as AgentBadgeWrapper } from "../../../../tool-card-HZ5bm7ue.js";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_tool_card = require("../../../../tool-card-
|
|
2
|
+
const require_tool_card = require("../../../../tool-card-BOLXeKIp.cjs");
|
|
3
3
|
exports.AgentBadge = require_tool_card.AgentBadge;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { i as AgentBadge } from "../../../../tool-card-
|
|
1
|
+
export { i as AgentBadge } from "../../../../tool-card-HZ5bm7ue.js";
|
|
@@ -3,32 +3,107 @@ const require_icons_Icon = require("../../../icons/Icon.cjs.js");
|
|
|
3
3
|
const require_Button = require("../../../Button-DLcMcuUL.cjs");
|
|
4
4
|
const require_Txt = require("../../../Txt-BnoSqxKE.cjs");
|
|
5
5
|
const require_Notice = require("../../../Notice-JZ7SW7q6.cjs");
|
|
6
|
+
const require_domains_chat_context_tool_call_context = require("../context/tool-call-context.cjs.js");
|
|
6
7
|
const require_domains_chat_tools_connect_prompt_tool_id = require("./connect-prompt-tool-id.cjs.js");
|
|
7
8
|
const require_domains_connect_types = require("../../connect/types.cjs.js");
|
|
9
|
+
const require_domains_chat_tools_tool_card_kind = require("./tool-card-kind.cjs.js");
|
|
8
10
|
const require_data_list = require("../../../data-list-C4Vmq5HA.cjs");
|
|
9
11
|
const require_domains_connect_use_user_connect = require("../../connect/use-user-connect.cjs.js");
|
|
10
12
|
const require_domains_connect_provider_logo = require("../../connect/provider-logo.cjs.js");
|
|
13
|
+
let react = require("react");
|
|
11
14
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
12
15
|
let lucide_react = require("lucide-react");
|
|
13
16
|
// src/domains/chat/tools/connect-prompt-tool.tsx
|
|
14
17
|
/**
|
|
18
|
+
* The prompt this card renders: the finished call's result, or the payload
|
|
19
|
+
* the run paused with while it waits for the user to connect.
|
|
20
|
+
*/
|
|
21
|
+
function connectPromptFor({ toolName, toolCallId, output, metadata }) {
|
|
22
|
+
if (require_domains_connect_types.isConnectPromptPayload(output)) return {
|
|
23
|
+
payload: output,
|
|
24
|
+
paused: false
|
|
25
|
+
};
|
|
26
|
+
const suspendPayload = require_domains_chat_tools_tool_card_kind.toolInteraction(metadata, toolName, toolCallId).suspended?.suspendPayload;
|
|
27
|
+
if (require_domains_connect_types.isConnectPromptPayload(suspendPayload)) return {
|
|
28
|
+
payload: suspendPayload,
|
|
29
|
+
paused: true
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** The resume tool's schema accepts; longer failure reasons are cut to fit. */
|
|
33
|
+
var MAX_RESUME_ERROR_LENGTH = 500;
|
|
34
|
+
/**
|
|
35
|
+
* What to resume the paused call with: the settled flow, or a failure when the
|
|
36
|
+
* connect session couldn't even be created (e.g. the connection limit).
|
|
37
|
+
*/
|
|
38
|
+
function connectPromptResumeFor(payload, settled, createError) {
|
|
39
|
+
const failed = (provider, error) => ({
|
|
40
|
+
provider,
|
|
41
|
+
status: "failed",
|
|
42
|
+
error: error.slice(0, MAX_RESUME_ERROR_LENGTH)
|
|
43
|
+
});
|
|
44
|
+
if (createError) {
|
|
45
|
+
const provider = payload.requestedProvider ?? payload.providers[0]?.id;
|
|
46
|
+
return provider ? failed(provider, createError.message) : void 0;
|
|
47
|
+
}
|
|
48
|
+
if (!settled) return void 0;
|
|
49
|
+
if (settled.status === "active") return {
|
|
50
|
+
provider: settled.provider,
|
|
51
|
+
status: "active"
|
|
52
|
+
};
|
|
53
|
+
return failed(settled.provider, settled.message ?? "The connection was not completed.");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
15
56
|
* Chat card for the built-in `request_connection` tool: the agent surfaces it
|
|
16
57
|
* when provider tools are unavailable because the caller has no connection.
|
|
17
58
|
* Each provider row offers a Connect action that creates a connect session on
|
|
18
59
|
* the mounted `/connect` routes, drives the OAuth flow in a popup through the
|
|
19
60
|
* server's launch gate (which verifies this browser owns the pending session
|
|
20
61
|
* before redirecting to the vendor), and polls until the connection settles.
|
|
62
|
+
*
|
|
63
|
+
* The tool pauses the agent run while the card is up. Once the connection
|
|
64
|
+
* settles, the card resumes the paused call with the outcome — connected, or
|
|
65
|
+
* the reason it failed — so the agent continues from the tool's result rather
|
|
66
|
+
* than a chat message.
|
|
21
67
|
*/
|
|
22
|
-
var ConnectPromptTool = ({ output, readOnly = false }) => {
|
|
23
|
-
|
|
68
|
+
var ConnectPromptTool = ({ toolName, toolCallId, output, metadata, readOnly = false }) => {
|
|
69
|
+
const prompt = connectPromptFor({
|
|
70
|
+
toolName,
|
|
71
|
+
toolCallId,
|
|
72
|
+
output,
|
|
73
|
+
metadata
|
|
74
|
+
});
|
|
75
|
+
if (!prompt) return null;
|
|
24
76
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConnectPromptCard, {
|
|
25
|
-
|
|
77
|
+
toolCallId,
|
|
78
|
+
payload: prompt.payload,
|
|
79
|
+
paused: prompt.paused,
|
|
26
80
|
readOnly
|
|
27
81
|
});
|
|
28
82
|
};
|
|
29
|
-
var ConnectPromptCard = ({ payload, readOnly }) => {
|
|
83
|
+
var ConnectPromptCard = ({ toolCallId, payload, paused, readOnly }) => {
|
|
30
84
|
const { pending, connect, connectedProviders, isCreating, createError, settled } = require_domains_connect_use_user_connect.usePendingConnect();
|
|
85
|
+
const { approveToolcall, isRunning, toolCallApprovals } = require_domains_chat_context_tool_call_context.useToolCall();
|
|
86
|
+
const resumedRef = (0, react.useRef)(null);
|
|
87
|
+
const alreadyResumed = toolCallApprovals[toolCallId] !== void 0;
|
|
31
88
|
const errorMessage = createError?.message ?? (settled?.status === "failed" ? settled.message ?? "The connection was not completed. Try connecting again." : null);
|
|
89
|
+
(0, react.useEffect)(() => {
|
|
90
|
+
if (readOnly || !paused || alreadyResumed || isRunning) return;
|
|
91
|
+
const outcome = createError ?? settled;
|
|
92
|
+
if (!outcome || resumedRef.current === outcome) return;
|
|
93
|
+
resumedRef.current = outcome;
|
|
94
|
+
const resume = connectPromptResumeFor(payload, settled, createError);
|
|
95
|
+
if (resume) approveToolcall(toolCallId, resume);
|
|
96
|
+
}, [
|
|
97
|
+
readOnly,
|
|
98
|
+
paused,
|
|
99
|
+
alreadyResumed,
|
|
100
|
+
isRunning,
|
|
101
|
+
settled,
|
|
102
|
+
createError,
|
|
103
|
+
payload,
|
|
104
|
+
approveToolcall,
|
|
105
|
+
toolCallId
|
|
106
|
+
]);
|
|
32
107
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
33
108
|
"aria-label": "Connect an account",
|
|
34
109
|
className: "my-2 space-y-3",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-prompt-tool.cjs.js","names":["isConnectPromptPayload","usePendingConnect","DataList","Txt","ProviderLogo","Icon","CheckIcon","Button","Notice"],"sources":["../../../../src/domains/chat/tools/connect-prompt-tool.tsx"],"sourcesContent":["import { CheckIcon } from 'lucide-react';\nimport type { ConnectPromptPayload } from '@/domains/connect';\nimport { isConnectPromptPayload, ProviderLogo, usePendingConnect } from '@/domains/connect';\nimport { Button } from '@/ds/components/Button';\nimport { DataList } from '@/ds/components/DataList';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { Icon } from '@/ds/icons/Icon';\n\nexport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\n\nexport interface ConnectPromptToolProps {\n output: unknown;\n /** Historical rendering: show the card but suppress the connect action. */\n readOnly?: boolean;\n}\n\n/**\n * Chat card for the built-in `request_connection` tool: the agent surfaces it\n * when provider tools are unavailable because the caller has no connection.\n * Each provider row offers a Connect action that creates a connect session on\n * the mounted `/connect` routes, drives the OAuth flow in a popup through the\n * server's launch gate (which verifies this browser owns the pending session\n * before redirecting to the vendor), and polls until the connection settles.\n */\nexport const ConnectPromptTool = ({ output, readOnly = false }: ConnectPromptToolProps) => {\n if (!isConnectPromptPayload(output)) return null;\n return <ConnectPromptCard payload={output} readOnly={readOnly} />;\n};\n\nconst ConnectPromptCard = ({ payload, readOnly }: { payload: ConnectPromptPayload; readOnly: boolean }) => {\n const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();\n\n const errorMessage =\n createError?.message ??\n (settled?.status === 'failed'\n ? (settled.message ?? 'The connection was not completed. Try connecting again.')\n : null);\n\n return (\n <section aria-label=\"Connect an account\" className=\"my-2 space-y-3\">\n <DataList columns=\"minmax(0, 1fr)\" fit=\"container\" scrollable={false}>\n <DataList.Subheader>\n <Txt as=\"h4\" variant=\"subheading\" tone=\"ink\">\n Connect an account\n </Txt>\n </DataList.Subheader>\n {payload.providers.map(provider => {\n const isConnected = provider.connected || connectedProviders.has(provider.id);\n const isPending = pending?.provider === provider.id;\n return (\n <DataList.RowStatic key={provider.id}>\n <DataList.Cell className=\"col-span-full py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <ProviderLogo logoUrl={provider.logoUrl} />\n <Txt as=\"span\" variant=\"body-sm\" tone=\"ink\" className=\"truncate\">\n {provider.displayName ?? provider.id}\n </Txt>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n {isPending ? (\n <Txt as=\"span\" variant=\"body-sm\" tone=\"muted\" role=\"status\">\n Waiting for authorization…\n </Txt>\n ) : null}\n {isPending && pending?.fallbackUrl ? (\n <a\n href={pending.fallbackUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-ui-md underline underline-offset-2\"\n >\n Open the connect window\n </a>\n ) : null}\n {isConnected ? (\n <Txt\n as=\"span\"\n variant=\"body-sm\"\n tone=\"muted\"\n className=\"flex min-w-24 items-center justify-center gap-1\"\n >\n <Icon size=\"sm\">\n <CheckIcon aria-hidden />\n </Icon>\n Connected\n </Txt>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n className=\"min-w-24\"\n disabled={readOnly || isCreating || isPending}\n onClick={() => connect(provider)}\n >\n Connect\n </Button>\n )}\n </div>\n </div>\n </DataList.Cell>\n </DataList.RowStatic>\n );\n })}\n </DataList>\n {errorMessage ? (\n <div role=\"alert\">\n <Notice variant=\"destructive\" title=\"Couldn't connect\">\n <Notice.Message>{errorMessage}</Notice.Message>\n </Notice>\n </div>\n ) : null}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"connect-prompt-tool.cjs.js","names":["isConnectPromptPayload","toolInteraction","usePendingConnect","useToolCall","useRef","DataList","Txt","ProviderLogo","Icon","CheckIcon","Button","Notice"],"sources":["../../../../src/domains/chat/tools/connect-prompt-tool.tsx"],"sourcesContent":["import { CheckIcon } from 'lucide-react';\nimport { useEffect, useRef } from 'react';\nimport type { MessageMetadata } from '../messages/message-metadata';\nimport { toolInteraction } from './tool-card-kind';\nimport { useToolCall } from '@/domains/chat/context/tool-call-context';\nimport type { ConnectPromptPayload, ConnectPromptResume, SettledConnect } from '@/domains/connect';\nimport { isConnectPromptPayload, ProviderLogo, usePendingConnect } from '@/domains/connect';\nimport { Button } from '@/ds/components/Button';\nimport { DataList } from '@/ds/components/DataList';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { Icon } from '@/ds/icons/Icon';\n\nexport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\n\nexport interface ConnectPromptToolProps {\n toolName: string;\n toolCallId: string;\n output: unknown;\n metadata?: MessageMetadata;\n /** Historical rendering: show the card but suppress the connect action. */\n readOnly?: boolean;\n}\n\n/**\n * The prompt this card renders: the finished call's result, or the payload\n * the run paused with while it waits for the user to connect.\n */\nfunction connectPromptFor({\n toolName,\n toolCallId,\n output,\n metadata,\n}: Pick<ConnectPromptToolProps, 'toolName' | 'toolCallId' | 'output' | 'metadata'>):\n | { payload: ConnectPromptPayload; paused: boolean }\n | undefined {\n if (isConnectPromptPayload(output)) return { payload: output, paused: false };\n const suspendPayload = toolInteraction(metadata, toolName, toolCallId).suspended?.suspendPayload;\n if (isConnectPromptPayload(suspendPayload)) return { payload: suspendPayload, paused: true };\n return undefined;\n}\n\n/** The resume tool's schema accepts; longer failure reasons are cut to fit. */\nconst MAX_RESUME_ERROR_LENGTH = 500;\n\n/**\n * What to resume the paused call with: the settled flow, or a failure when the\n * connect session couldn't even be created (e.g. the connection limit).\n */\nfunction connectPromptResumeFor(\n payload: ConnectPromptPayload,\n settled: SettledConnect | null,\n createError: Error | null,\n): ConnectPromptResume | undefined {\n const failed = (provider: string, error: string): ConnectPromptResume => ({\n provider,\n status: 'failed',\n error: error.slice(0, MAX_RESUME_ERROR_LENGTH),\n });\n if (createError) {\n const provider = payload.requestedProvider ?? payload.providers[0]?.id;\n return provider ? failed(provider, createError.message) : undefined;\n }\n if (!settled) return undefined;\n if (settled.status === 'active') return { provider: settled.provider, status: 'active' };\n return failed(settled.provider, settled.message ?? 'The connection was not completed.');\n}\n\n/**\n * Chat card for the built-in `request_connection` tool: the agent surfaces it\n * when provider tools are unavailable because the caller has no connection.\n * Each provider row offers a Connect action that creates a connect session on\n * the mounted `/connect` routes, drives the OAuth flow in a popup through the\n * server's launch gate (which verifies this browser owns the pending session\n * before redirecting to the vendor), and polls until the connection settles.\n *\n * The tool pauses the agent run while the card is up. Once the connection\n * settles, the card resumes the paused call with the outcome — connected, or\n * the reason it failed — so the agent continues from the tool's result rather\n * than a chat message.\n */\nexport const ConnectPromptTool = ({\n toolName,\n toolCallId,\n output,\n metadata,\n readOnly = false,\n}: ConnectPromptToolProps) => {\n const prompt = connectPromptFor({ toolName, toolCallId, output, metadata });\n if (!prompt) return null;\n return (\n <ConnectPromptCard toolCallId={toolCallId} payload={prompt.payload} paused={prompt.paused} readOnly={readOnly} />\n );\n};\n\ninterface ConnectPromptCardProps {\n toolCallId: string;\n payload: ConnectPromptPayload;\n /** The run is waiting on this call, so an activation resumes it. */\n paused: boolean;\n readOnly: boolean;\n}\n\nconst ConnectPromptCard = ({ toolCallId, payload, paused, readOnly }: ConnectPromptCardProps) => {\n const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();\n const { approveToolcall, isRunning, toolCallApprovals } = useToolCall();\n // The outcome (settled flow or session-creation error) this call was resumed with.\n const resumedRef = useRef<SettledConnect | Error | null>(null);\n const alreadyResumed = toolCallApprovals[toolCallId] !== undefined;\n\n const errorMessage =\n createError?.message ??\n (settled?.status === 'failed'\n ? (settled.message ?? 'The connection was not completed. Try connecting again.')\n : null);\n\n // Resume once per outcome, success or failure, so the agent sees what\n // happened in the tool result. A run that is still streaming can't be\n // resumed yet, so an outcome that lands mid-run waits for it to stop.\n useEffect(() => {\n if (readOnly || !paused || alreadyResumed || isRunning) return;\n const outcome = createError ?? settled;\n if (!outcome || resumedRef.current === outcome) return;\n resumedRef.current = outcome;\n const resume = connectPromptResumeFor(payload, settled, createError);\n if (resume) approveToolcall(toolCallId, resume);\n }, [readOnly, paused, alreadyResumed, isRunning, settled, createError, payload, approveToolcall, toolCallId]);\n\n return (\n <section aria-label=\"Connect an account\" className=\"my-2 space-y-3\">\n <DataList columns=\"minmax(0, 1fr)\" fit=\"container\" scrollable={false}>\n <DataList.Subheader>\n <Txt as=\"h4\" variant=\"subheading\" tone=\"ink\">\n Connect an account\n </Txt>\n </DataList.Subheader>\n {payload.providers.map(provider => {\n const isConnected = provider.connected || connectedProviders.has(provider.id);\n const isPending = pending?.provider === provider.id;\n return (\n <DataList.RowStatic key={provider.id}>\n <DataList.Cell className=\"col-span-full py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <ProviderLogo logoUrl={provider.logoUrl} />\n <Txt as=\"span\" variant=\"body-sm\" tone=\"ink\" className=\"truncate\">\n {provider.displayName ?? provider.id}\n </Txt>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n {isPending ? (\n <Txt as=\"span\" variant=\"body-sm\" tone=\"muted\" role=\"status\">\n Waiting for authorization…\n </Txt>\n ) : null}\n {isPending && pending?.fallbackUrl ? (\n <a\n href={pending.fallbackUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-ui-md underline underline-offset-2\"\n >\n Open the connect window\n </a>\n ) : null}\n {isConnected ? (\n <Txt\n as=\"span\"\n variant=\"body-sm\"\n tone=\"muted\"\n className=\"flex min-w-24 items-center justify-center gap-1\"\n >\n <Icon size=\"sm\">\n <CheckIcon aria-hidden />\n </Icon>\n Connected\n </Txt>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n className=\"min-w-24\"\n disabled={readOnly || isCreating || isPending}\n onClick={() => connect(provider)}\n >\n Connect\n </Button>\n )}\n </div>\n </div>\n </DataList.Cell>\n </DataList.RowStatic>\n );\n })}\n </DataList>\n {errorMessage ? (\n <div role=\"alert\">\n <Notice variant=\"destructive\" title=\"Couldn't connect\">\n <Notice.Message>{errorMessage}</Notice.Message>\n </Notice>\n </div>\n ) : null}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
@@ -2,32 +2,107 @@ import { Icon } from "../../../icons/Icon.es.js";
|
|
|
2
2
|
import { t as Button } from "../../../Button-CLXTbAY8.js";
|
|
3
3
|
import { t as Txt } from "../../../Txt-BM6TVV_c.js";
|
|
4
4
|
import { t as Notice } from "../../../Notice-C-8oOZZF.js";
|
|
5
|
+
import { useToolCall } from "../context/tool-call-context.es.js";
|
|
5
6
|
import { CONNECT_PROMPT_TOOL_NAME } from "./connect-prompt-tool-id.es.js";
|
|
6
7
|
import { isConnectPromptPayload } from "../../connect/types.es.js";
|
|
8
|
+
import { toolInteraction } from "./tool-card-kind.es.js";
|
|
7
9
|
import { t as DataList } from "../../../data-list-DHCOoLmj.js";
|
|
8
10
|
import { usePendingConnect } from "../../connect/use-user-connect.es.js";
|
|
9
11
|
import { ProviderLogo } from "../../connect/provider-logo.es.js";
|
|
12
|
+
import { useEffect, useRef } from "react";
|
|
10
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
14
|
import { CheckIcon } from "lucide-react";
|
|
12
15
|
// src/domains/chat/tools/connect-prompt-tool.tsx
|
|
13
16
|
/**
|
|
17
|
+
* The prompt this card renders: the finished call's result, or the payload
|
|
18
|
+
* the run paused with while it waits for the user to connect.
|
|
19
|
+
*/
|
|
20
|
+
function connectPromptFor({ toolName, toolCallId, output, metadata }) {
|
|
21
|
+
if (isConnectPromptPayload(output)) return {
|
|
22
|
+
payload: output,
|
|
23
|
+
paused: false
|
|
24
|
+
};
|
|
25
|
+
const suspendPayload = toolInteraction(metadata, toolName, toolCallId).suspended?.suspendPayload;
|
|
26
|
+
if (isConnectPromptPayload(suspendPayload)) return {
|
|
27
|
+
payload: suspendPayload,
|
|
28
|
+
paused: true
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** The resume tool's schema accepts; longer failure reasons are cut to fit. */
|
|
32
|
+
var MAX_RESUME_ERROR_LENGTH = 500;
|
|
33
|
+
/**
|
|
34
|
+
* What to resume the paused call with: the settled flow, or a failure when the
|
|
35
|
+
* connect session couldn't even be created (e.g. the connection limit).
|
|
36
|
+
*/
|
|
37
|
+
function connectPromptResumeFor(payload, settled, createError) {
|
|
38
|
+
const failed = (provider, error) => ({
|
|
39
|
+
provider,
|
|
40
|
+
status: "failed",
|
|
41
|
+
error: error.slice(0, MAX_RESUME_ERROR_LENGTH)
|
|
42
|
+
});
|
|
43
|
+
if (createError) {
|
|
44
|
+
const provider = payload.requestedProvider ?? payload.providers[0]?.id;
|
|
45
|
+
return provider ? failed(provider, createError.message) : void 0;
|
|
46
|
+
}
|
|
47
|
+
if (!settled) return void 0;
|
|
48
|
+
if (settled.status === "active") return {
|
|
49
|
+
provider: settled.provider,
|
|
50
|
+
status: "active"
|
|
51
|
+
};
|
|
52
|
+
return failed(settled.provider, settled.message ?? "The connection was not completed.");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
14
55
|
* Chat card for the built-in `request_connection` tool: the agent surfaces it
|
|
15
56
|
* when provider tools are unavailable because the caller has no connection.
|
|
16
57
|
* Each provider row offers a Connect action that creates a connect session on
|
|
17
58
|
* the mounted `/connect` routes, drives the OAuth flow in a popup through the
|
|
18
59
|
* server's launch gate (which verifies this browser owns the pending session
|
|
19
60
|
* before redirecting to the vendor), and polls until the connection settles.
|
|
61
|
+
*
|
|
62
|
+
* The tool pauses the agent run while the card is up. Once the connection
|
|
63
|
+
* settles, the card resumes the paused call with the outcome — connected, or
|
|
64
|
+
* the reason it failed — so the agent continues from the tool's result rather
|
|
65
|
+
* than a chat message.
|
|
20
66
|
*/
|
|
21
|
-
var ConnectPromptTool = ({ output, readOnly = false }) => {
|
|
22
|
-
|
|
67
|
+
var ConnectPromptTool = ({ toolName, toolCallId, output, metadata, readOnly = false }) => {
|
|
68
|
+
const prompt = connectPromptFor({
|
|
69
|
+
toolName,
|
|
70
|
+
toolCallId,
|
|
71
|
+
output,
|
|
72
|
+
metadata
|
|
73
|
+
});
|
|
74
|
+
if (!prompt) return null;
|
|
23
75
|
return /* @__PURE__ */ jsx(ConnectPromptCard, {
|
|
24
|
-
|
|
76
|
+
toolCallId,
|
|
77
|
+
payload: prompt.payload,
|
|
78
|
+
paused: prompt.paused,
|
|
25
79
|
readOnly
|
|
26
80
|
});
|
|
27
81
|
};
|
|
28
|
-
var ConnectPromptCard = ({ payload, readOnly }) => {
|
|
82
|
+
var ConnectPromptCard = ({ toolCallId, payload, paused, readOnly }) => {
|
|
29
83
|
const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();
|
|
84
|
+
const { approveToolcall, isRunning, toolCallApprovals } = useToolCall();
|
|
85
|
+
const resumedRef = useRef(null);
|
|
86
|
+
const alreadyResumed = toolCallApprovals[toolCallId] !== void 0;
|
|
30
87
|
const errorMessage = createError?.message ?? (settled?.status === "failed" ? settled.message ?? "The connection was not completed. Try connecting again." : null);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (readOnly || !paused || alreadyResumed || isRunning) return;
|
|
90
|
+
const outcome = createError ?? settled;
|
|
91
|
+
if (!outcome || resumedRef.current === outcome) return;
|
|
92
|
+
resumedRef.current = outcome;
|
|
93
|
+
const resume = connectPromptResumeFor(payload, settled, createError);
|
|
94
|
+
if (resume) approveToolcall(toolCallId, resume);
|
|
95
|
+
}, [
|
|
96
|
+
readOnly,
|
|
97
|
+
paused,
|
|
98
|
+
alreadyResumed,
|
|
99
|
+
isRunning,
|
|
100
|
+
settled,
|
|
101
|
+
createError,
|
|
102
|
+
payload,
|
|
103
|
+
approveToolcall,
|
|
104
|
+
toolCallId
|
|
105
|
+
]);
|
|
31
106
|
return /* @__PURE__ */ jsxs("section", {
|
|
32
107
|
"aria-label": "Connect an account",
|
|
33
108
|
className: "my-2 space-y-3",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-prompt-tool.es.js","names":[],"sources":["../../../../src/domains/chat/tools/connect-prompt-tool.tsx"],"sourcesContent":["import { CheckIcon } from 'lucide-react';\nimport type { ConnectPromptPayload } from '@/domains/connect';\nimport { isConnectPromptPayload, ProviderLogo, usePendingConnect } from '@/domains/connect';\nimport { Button } from '@/ds/components/Button';\nimport { DataList } from '@/ds/components/DataList';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { Icon } from '@/ds/icons/Icon';\n\nexport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\n\nexport interface ConnectPromptToolProps {\n output: unknown;\n /** Historical rendering: show the card but suppress the connect action. */\n readOnly?: boolean;\n}\n\n/**\n * Chat card for the built-in `request_connection` tool: the agent surfaces it\n * when provider tools are unavailable because the caller has no connection.\n * Each provider row offers a Connect action that creates a connect session on\n * the mounted `/connect` routes, drives the OAuth flow in a popup through the\n * server's launch gate (which verifies this browser owns the pending session\n * before redirecting to the vendor), and polls until the connection settles.\n */\nexport const ConnectPromptTool = ({ output, readOnly = false }: ConnectPromptToolProps) => {\n if (!isConnectPromptPayload(output)) return null;\n return <ConnectPromptCard payload={output} readOnly={readOnly} />;\n};\n\nconst ConnectPromptCard = ({ payload, readOnly }: { payload: ConnectPromptPayload; readOnly: boolean }) => {\n const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();\n\n const errorMessage =\n createError?.message ??\n (settled?.status === 'failed'\n ? (settled.message ?? 'The connection was not completed. Try connecting again.')\n : null);\n\n return (\n <section aria-label=\"Connect an account\" className=\"my-2 space-y-3\">\n <DataList columns=\"minmax(0, 1fr)\" fit=\"container\" scrollable={false}>\n <DataList.Subheader>\n <Txt as=\"h4\" variant=\"subheading\" tone=\"ink\">\n Connect an account\n </Txt>\n </DataList.Subheader>\n {payload.providers.map(provider => {\n const isConnected = provider.connected || connectedProviders.has(provider.id);\n const isPending = pending?.provider === provider.id;\n return (\n <DataList.RowStatic key={provider.id}>\n <DataList.Cell className=\"col-span-full py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <ProviderLogo logoUrl={provider.logoUrl} />\n <Txt as=\"span\" variant=\"body-sm\" tone=\"ink\" className=\"truncate\">\n {provider.displayName ?? provider.id}\n </Txt>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n {isPending ? (\n <Txt as=\"span\" variant=\"body-sm\" tone=\"muted\" role=\"status\">\n Waiting for authorization…\n </Txt>\n ) : null}\n {isPending && pending?.fallbackUrl ? (\n <a\n href={pending.fallbackUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-ui-md underline underline-offset-2\"\n >\n Open the connect window\n </a>\n ) : null}\n {isConnected ? (\n <Txt\n as=\"span\"\n variant=\"body-sm\"\n tone=\"muted\"\n className=\"flex min-w-24 items-center justify-center gap-1\"\n >\n <Icon size=\"sm\">\n <CheckIcon aria-hidden />\n </Icon>\n Connected\n </Txt>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n className=\"min-w-24\"\n disabled={readOnly || isCreating || isPending}\n onClick={() => connect(provider)}\n >\n Connect\n </Button>\n )}\n </div>\n </div>\n </DataList.Cell>\n </DataList.RowStatic>\n );\n })}\n </DataList>\n {errorMessage ? (\n <div role=\"alert\">\n <Notice variant=\"destructive\" title=\"Couldn't connect\">\n <Notice.Message>{errorMessage}</Notice.Message>\n </Notice>\n </div>\n ) : null}\n </section>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,qBAAqB,EAAE,QAAQ,WAAW,YAAoC;CACzF,IAAI,CAAC,uBAAuB,MAAM,GAAG,OAAO;CAC5C,OAAO,oBAAC,mBAAD;EAAmB,SAAS;EAAkB;CAAW,CAAA;AAClE;AAEA,IAAM,qBAAqB,EAAE,SAAS,eAAqE;CACzG,MAAM,EAAE,SAAS,SAAS,oBAAoB,YAAY,aAAa,YAAY,kBAAkB;CAErG,MAAM,eACJ,aAAa,YACZ,SAAS,WAAW,WAChB,QAAQ,WAAW,4DACpB;CAEN,OACE,qBAAC,WAAD;EAAS,cAAW;EAAqB,WAAU;EAAnD,UAAA,CACE,qBAAC,UAAD;GAAU,SAAQ;GAAiB,KAAI;GAAY,YAAY;GAA/D,UAAA,CACE,oBAAC,SAAS,WAAV,EAAA,UACE,oBAAC,KAAD;IAAK,IAAG;IAAK,SAAQ;IAAa,MAAK;IAAM,UAAA;GAExC,CAAA,EACa,CAAA,GACnB,QAAQ,UAAU,KAAI,aAAY;IACjC,MAAM,cAAc,SAAS,aAAa,mBAAmB,IAAI,SAAS,EAAE;IAC5E,MAAM,YAAY,SAAS,aAAa,SAAS;IACjD,OACE,oBAAC,SAAS,WAAV,EAAA,UACE,oBAAC,SAAS,MAAV;KAAe,WAAU;KACvB,UAAA,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,cAAD,EAAc,SAAS,SAAS,QAAU,CAAA,GAC1C,oBAAC,KAAD;QAAK,IAAG;QAAO,SAAQ;QAAU,MAAK;QAAM,WAAU;QACnD,UAAA,SAAS,eAAe,SAAS;OAC/B,CAAA,CACF;MACL,CAAA,GAAA,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA;QACG,YACC,oBAAC,KAAD;SAAK,IAAG;SAAO,SAAQ;SAAU,MAAK;SAAQ,MAAK;SAAS,UAAA;QAEvD,CAAA,IACH;QACH,aAAa,SAAS,cACrB,oBAAC,KAAD;SACE,MAAM,QAAQ;SACd,QAAO;SACP,KAAI;SACJ,WAAU;SACX,UAAA;QAEE,CAAA,IACD;QACH,cACC,qBAAC,KAAD;SACE,IAAG;SACH,SAAQ;SACR,MAAK;SACL,WAAU;SAJZ,UAAA,CAME,oBAAC,MAAD;UAAM,MAAK;UACT,UAAA,oBAAC,WAAD,EAAW,eAAA,KAAa,CAAA;SACpB,CAAA,GAAC,WAEJ;QAEL,CAAA,IAAA,oBAAC,QAAD;SACE,SAAQ;SACR,MAAK;SACL,WAAU;SACV,UAAU,YAAY,cAAc;SACpC,eAAe,QAAQ,QAAQ;SAChC,UAAA;QAEO,CAAA;OAEP;MACF,CAAA,CAAA;;IACQ,CAAA,EACG,GAnDK,SAAS,EAmDd;GAExB,CAAC,CACO;EACT,CAAA,GAAA,eACC,oBAAC,OAAD;GAAK,MAAK;GACR,UAAA,oBAAC,QAAD;IAAQ,SAAQ;IAAc,OAAM;IAClC,UAAA,oBAAC,OAAO,SAAR,EAAA,UAAiB,aAA6B,CAAA;GACxC,CAAA;EACL,CAAA,IACH,IACG;;AAEb"}
|
|
1
|
+
{"version":3,"file":"connect-prompt-tool.es.js","names":[],"sources":["../../../../src/domains/chat/tools/connect-prompt-tool.tsx"],"sourcesContent":["import { CheckIcon } from 'lucide-react';\nimport { useEffect, useRef } from 'react';\nimport type { MessageMetadata } from '../messages/message-metadata';\nimport { toolInteraction } from './tool-card-kind';\nimport { useToolCall } from '@/domains/chat/context/tool-call-context';\nimport type { ConnectPromptPayload, ConnectPromptResume, SettledConnect } from '@/domains/connect';\nimport { isConnectPromptPayload, ProviderLogo, usePendingConnect } from '@/domains/connect';\nimport { Button } from '@/ds/components/Button';\nimport { DataList } from '@/ds/components/DataList';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { Icon } from '@/ds/icons/Icon';\n\nexport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\n\nexport interface ConnectPromptToolProps {\n toolName: string;\n toolCallId: string;\n output: unknown;\n metadata?: MessageMetadata;\n /** Historical rendering: show the card but suppress the connect action. */\n readOnly?: boolean;\n}\n\n/**\n * The prompt this card renders: the finished call's result, or the payload\n * the run paused with while it waits for the user to connect.\n */\nfunction connectPromptFor({\n toolName,\n toolCallId,\n output,\n metadata,\n}: Pick<ConnectPromptToolProps, 'toolName' | 'toolCallId' | 'output' | 'metadata'>):\n | { payload: ConnectPromptPayload; paused: boolean }\n | undefined {\n if (isConnectPromptPayload(output)) return { payload: output, paused: false };\n const suspendPayload = toolInteraction(metadata, toolName, toolCallId).suspended?.suspendPayload;\n if (isConnectPromptPayload(suspendPayload)) return { payload: suspendPayload, paused: true };\n return undefined;\n}\n\n/** The resume tool's schema accepts; longer failure reasons are cut to fit. */\nconst MAX_RESUME_ERROR_LENGTH = 500;\n\n/**\n * What to resume the paused call with: the settled flow, or a failure when the\n * connect session couldn't even be created (e.g. the connection limit).\n */\nfunction connectPromptResumeFor(\n payload: ConnectPromptPayload,\n settled: SettledConnect | null,\n createError: Error | null,\n): ConnectPromptResume | undefined {\n const failed = (provider: string, error: string): ConnectPromptResume => ({\n provider,\n status: 'failed',\n error: error.slice(0, MAX_RESUME_ERROR_LENGTH),\n });\n if (createError) {\n const provider = payload.requestedProvider ?? payload.providers[0]?.id;\n return provider ? failed(provider, createError.message) : undefined;\n }\n if (!settled) return undefined;\n if (settled.status === 'active') return { provider: settled.provider, status: 'active' };\n return failed(settled.provider, settled.message ?? 'The connection was not completed.');\n}\n\n/**\n * Chat card for the built-in `request_connection` tool: the agent surfaces it\n * when provider tools are unavailable because the caller has no connection.\n * Each provider row offers a Connect action that creates a connect session on\n * the mounted `/connect` routes, drives the OAuth flow in a popup through the\n * server's launch gate (which verifies this browser owns the pending session\n * before redirecting to the vendor), and polls until the connection settles.\n *\n * The tool pauses the agent run while the card is up. Once the connection\n * settles, the card resumes the paused call with the outcome — connected, or\n * the reason it failed — so the agent continues from the tool's result rather\n * than a chat message.\n */\nexport const ConnectPromptTool = ({\n toolName,\n toolCallId,\n output,\n metadata,\n readOnly = false,\n}: ConnectPromptToolProps) => {\n const prompt = connectPromptFor({ toolName, toolCallId, output, metadata });\n if (!prompt) return null;\n return (\n <ConnectPromptCard toolCallId={toolCallId} payload={prompt.payload} paused={prompt.paused} readOnly={readOnly} />\n );\n};\n\ninterface ConnectPromptCardProps {\n toolCallId: string;\n payload: ConnectPromptPayload;\n /** The run is waiting on this call, so an activation resumes it. */\n paused: boolean;\n readOnly: boolean;\n}\n\nconst ConnectPromptCard = ({ toolCallId, payload, paused, readOnly }: ConnectPromptCardProps) => {\n const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();\n const { approveToolcall, isRunning, toolCallApprovals } = useToolCall();\n // The outcome (settled flow or session-creation error) this call was resumed with.\n const resumedRef = useRef<SettledConnect | Error | null>(null);\n const alreadyResumed = toolCallApprovals[toolCallId] !== undefined;\n\n const errorMessage =\n createError?.message ??\n (settled?.status === 'failed'\n ? (settled.message ?? 'The connection was not completed. Try connecting again.')\n : null);\n\n // Resume once per outcome, success or failure, so the agent sees what\n // happened in the tool result. A run that is still streaming can't be\n // resumed yet, so an outcome that lands mid-run waits for it to stop.\n useEffect(() => {\n if (readOnly || !paused || alreadyResumed || isRunning) return;\n const outcome = createError ?? settled;\n if (!outcome || resumedRef.current === outcome) return;\n resumedRef.current = outcome;\n const resume = connectPromptResumeFor(payload, settled, createError);\n if (resume) approveToolcall(toolCallId, resume);\n }, [readOnly, paused, alreadyResumed, isRunning, settled, createError, payload, approveToolcall, toolCallId]);\n\n return (\n <section aria-label=\"Connect an account\" className=\"my-2 space-y-3\">\n <DataList columns=\"minmax(0, 1fr)\" fit=\"container\" scrollable={false}>\n <DataList.Subheader>\n <Txt as=\"h4\" variant=\"subheading\" tone=\"ink\">\n Connect an account\n </Txt>\n </DataList.Subheader>\n {payload.providers.map(provider => {\n const isConnected = provider.connected || connectedProviders.has(provider.id);\n const isPending = pending?.provider === provider.id;\n return (\n <DataList.RowStatic key={provider.id}>\n <DataList.Cell className=\"col-span-full py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <ProviderLogo logoUrl={provider.logoUrl} />\n <Txt as=\"span\" variant=\"body-sm\" tone=\"ink\" className=\"truncate\">\n {provider.displayName ?? provider.id}\n </Txt>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n {isPending ? (\n <Txt as=\"span\" variant=\"body-sm\" tone=\"muted\" role=\"status\">\n Waiting for authorization…\n </Txt>\n ) : null}\n {isPending && pending?.fallbackUrl ? (\n <a\n href={pending.fallbackUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-ui-md underline underline-offset-2\"\n >\n Open the connect window\n </a>\n ) : null}\n {isConnected ? (\n <Txt\n as=\"span\"\n variant=\"body-sm\"\n tone=\"muted\"\n className=\"flex min-w-24 items-center justify-center gap-1\"\n >\n <Icon size=\"sm\">\n <CheckIcon aria-hidden />\n </Icon>\n Connected\n </Txt>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n className=\"min-w-24\"\n disabled={readOnly || isCreating || isPending}\n onClick={() => connect(provider)}\n >\n Connect\n </Button>\n )}\n </div>\n </div>\n </DataList.Cell>\n </DataList.RowStatic>\n );\n })}\n </DataList>\n {errorMessage ? (\n <div role=\"alert\">\n <Notice variant=\"destructive\" title=\"Couldn't connect\">\n <Notice.Message>{errorMessage}</Notice.Message>\n </Notice>\n </div>\n ) : null}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
@@ -97,23 +97,21 @@ function viewRequest({ method, params }) {
|
|
|
97
97
|
* before the call can continue, so answers are held until the last one is given, then sent together.
|
|
98
98
|
*/
|
|
99
99
|
function McpInputRequestTool({ payload, onSubmit, isSubmitting, error, readOnly, notice }) {
|
|
100
|
-
const [answers, setAnswers] = (0, react.useState)(
|
|
100
|
+
const [answers, setAnswers] = (0, react.useState)(() => /* @__PURE__ */ new Map());
|
|
101
101
|
const keys = Object.keys(payload.requests);
|
|
102
102
|
const tooLarge = (0, react.useMemo)(() => require_domains_chat_tools_mcp_input_request.inputRequestTooLarge(payload), [payload]);
|
|
103
|
-
const send = (
|
|
104
|
-
|
|
103
|
+
const send = (next) => {
|
|
104
|
+
const inputResponses = Object.fromEntries(next);
|
|
105
|
+
Promise.resolve(onSubmit({ inputResponses })).catch(() => setAnswers(/* @__PURE__ */ new Map()));
|
|
105
106
|
};
|
|
106
107
|
const answer = (key, response) => {
|
|
107
|
-
const next =
|
|
108
|
-
...answers,
|
|
109
|
-
[key]: response
|
|
110
|
-
};
|
|
108
|
+
const next = new Map(answers).set(key, response);
|
|
111
109
|
setAnswers(next);
|
|
112
|
-
if (!keys.every((requestKey) => next
|
|
110
|
+
if (!keys.every((requestKey) => next.has(requestKey))) return;
|
|
113
111
|
send(next);
|
|
114
112
|
};
|
|
115
113
|
const cancelAll = () => {
|
|
116
|
-
const next =
|
|
114
|
+
const next = new Map(keys.map((key) => [key, { action: "cancel" }]));
|
|
117
115
|
setAnswers(next);
|
|
118
116
|
send(next);
|
|
119
117
|
};
|
|
@@ -139,15 +137,15 @@ function McpInputRequestTool({ payload, onSubmit, isSubmitting, error, readOnly,
|
|
|
139
137
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Button.Button, {
|
|
140
138
|
type: "button",
|
|
141
139
|
variant: "ghost",
|
|
142
|
-
disabled: isSubmitting ||
|
|
140
|
+
disabled: isSubmitting || answers.size > 0,
|
|
143
141
|
onClick: cancelAll,
|
|
144
142
|
children: "Cancel"
|
|
145
143
|
})
|
|
146
144
|
})] }) : Object.entries(payload.requests).map(([key, request]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InputRequest, {
|
|
147
145
|
requestKey: key,
|
|
148
146
|
request,
|
|
149
|
-
answered: answers
|
|
150
|
-
disabled: readOnly || isSubmitting || answers
|
|
147
|
+
answered: answers.get(key)?.action,
|
|
148
|
+
disabled: readOnly || isSubmitting || answers.has(key),
|
|
151
149
|
readOnly,
|
|
152
150
|
onAnswer: (response) => answer(key, response)
|
|
153
151
|
}, key)),
|