@sybilion/uilib 1.3.21 → 1.3.23
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/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +21 -4
- package/dist/esm/components/widgets/DriversComparisonChart/DriversComparisonChart.js +139 -0
- package/dist/esm/components/widgets/DriversComparisonChart/DriversComparisonChart.styl.js +7 -0
- package/dist/esm/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js +159 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/types/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.types.d.ts +9 -1
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +2 -2
- package/dist/esm/types/src/components/ui/Chat/index.d.ts +1 -0
- package/dist/esm/types/src/components/widgets/DriversComparisonChart/DriversComparisonChart.d.ts +18 -0
- package/dist/esm/types/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.d.ts +26 -0
- package/dist/esm/types/src/components/widgets/DriversComparisonChart/index.d.ts +2 -0
- package/dist/esm/types/src/docs/pages/DriversComparisonChartPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +1 -0
- package/dist/esm/utils/chartConnectionPoint.js +9 -1
- package/package.json +1 -1
- package/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.types.ts +17 -1
- package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +23 -4
- package/src/components/ui/Chat/index.ts +5 -0
- package/src/components/widgets/DriversComparisonChart/DriversComparisonChart.styl +145 -0
- package/src/components/widgets/DriversComparisonChart/DriversComparisonChart.styl.d.ts +29 -0
- package/src/components/widgets/DriversComparisonChart/DriversComparisonChart.tsx +325 -0
- package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.ts +206 -0
- package/src/components/widgets/DriversComparisonChart/index.ts +13 -0
- package/src/docs/pages/DriversComparisonChartPage.tsx +174 -0
- package/src/docs/registry.ts +6 -0
- package/src/index.ts +1 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
3
3
|
import { MessageRole, GENERATING_DASHBOARD_SYSTEM_TEXT } from '../Chat.types.js';
|
|
4
|
-
import { isGraphIntakeAssistantStepComplete, matchUserTextToQuickReply, parseScriptLine, textHasQuickReplyMarkers, branchKeysUsedFromChatHistory, branchKeysUsedByUserMessages, extractQuickReplyLabelKeyPairsFromText, entryBranchKeyBeforeLastAssistant
|
|
4
|
+
import { isGraphIntakeAssistantStepComplete, matchUserTextToQuickReply, isPresetScriptGraph, branchesFromPresetScriptGraph, parseScriptLine, textHasQuickReplyMarkers, branchKeysUsedFromChatHistory, branchKeysUsedByUserMessages, extractQuickReplyLabelKeyPairsFromText, entryBranchKeyBeforeLastAssistant } from '../ChatMessage/presetScript.js';
|
|
5
5
|
import { buildChatSendMessagePayload, displayTextFromSendPayload } from '../buildChatSendMessagePayload.js';
|
|
6
6
|
import { usedPresetIdsFromMessages, formatChatTranscript } from '../chat-preset-utils.js';
|
|
7
7
|
import { useChatsForScopeId, useChat, useChatOutboundPending, useSyncChatPanelBusy, isChatEmpty } from '../../../../contexts/chat-context.js';
|
|
@@ -425,7 +425,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
425
425
|
onMessage,
|
|
426
426
|
onScriptComplete,
|
|
427
427
|
]);
|
|
428
|
-
const submitPreset = async (preset) => {
|
|
428
|
+
const submitPreset = useCallback(async (preset) => {
|
|
429
429
|
const script = preset.script;
|
|
430
430
|
const scriptGraph = isPresetScriptGraph(script);
|
|
431
431
|
const hasLinearScript = Array.isArray(script) && script.length > 0;
|
|
@@ -518,7 +518,24 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
518
518
|
finally {
|
|
519
519
|
setLocalUiBusy(false);
|
|
520
520
|
}
|
|
521
|
-
}
|
|
521
|
+
}, [
|
|
522
|
+
currentChatId,
|
|
523
|
+
endLocalDemoFlow,
|
|
524
|
+
handlePromptSubmit,
|
|
525
|
+
addMessage,
|
|
526
|
+
presetsWithFreeform,
|
|
527
|
+
]);
|
|
528
|
+
const resolvedEmptyState = useMemo(() => {
|
|
529
|
+
if (!emptyState)
|
|
530
|
+
return undefined;
|
|
531
|
+
const { additionalContent, ...rest } = emptyState;
|
|
532
|
+
return {
|
|
533
|
+
...rest,
|
|
534
|
+
additionalContent: typeof additionalContent === 'function'
|
|
535
|
+
? additionalContent({ submitPreset })
|
|
536
|
+
: additionalContent,
|
|
537
|
+
};
|
|
538
|
+
}, [emptyState, submitPreset]);
|
|
522
539
|
const activeScript = currentChatId
|
|
523
540
|
? scriptByChatId[currentChatId]
|
|
524
541
|
: undefined;
|
|
@@ -765,7 +782,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
765
782
|
onPromptSubmit: handlePromptSubmit,
|
|
766
783
|
onChatDeleted: endLocalDemoFlow,
|
|
767
784
|
promptPrefill: promptLinkPrefill,
|
|
768
|
-
emptyState,
|
|
785
|
+
emptyState: resolvedEmptyState,
|
|
769
786
|
allowedAttachments,
|
|
770
787
|
allowPdfAttachments,
|
|
771
788
|
onAttachmentsDropped,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
4
|
+
import { ChartEmptyState } from '../../ui/Chart/components/ChartEmptyState/ChartEmptyState.js';
|
|
5
|
+
import { ChartAreaInteractive } from '../../ui/ChartAreaInteractive/ChartAreaInteractive.js';
|
|
6
|
+
import { getForecastColor } from '../../ui/ChartAreaInteractive/ChartLines.js';
|
|
7
|
+
import '../../ui/Page/AppShell/AppShell.js';
|
|
8
|
+
import 'react-router-dom';
|
|
9
|
+
import '../../ui/Sidebar/Sidebar.js';
|
|
10
|
+
import 'lucide-react';
|
|
11
|
+
import '../../ui/Page/Breadcrumbs/Breadcrumbs.styl.js';
|
|
12
|
+
import '../../ui/Page/pageContext.js';
|
|
13
|
+
import '@radix-ui/react-tooltip';
|
|
14
|
+
import '../../ui/Tooltip/Tooltip.styl.js';
|
|
15
|
+
import '../../ui/Page/PageHeader/PageHeader.styl.js';
|
|
16
|
+
import '../../ui/Page/PageEmptyCanvas/PageEmptyCanvas.styl.js';
|
|
17
|
+
import '../../ui/Page/PageContent/PageContent.styl.js';
|
|
18
|
+
import '@homecode/ui';
|
|
19
|
+
import '../../ui/Page/PageScroll/PageScroll.styl.js';
|
|
20
|
+
import { PageXScroll } from '../../ui/Page/PageXScroll/PageXScroll.js';
|
|
21
|
+
import '../../ui/Page/PageFooter/PageFooter.styl.js';
|
|
22
|
+
import '@radix-ui/react-tabs';
|
|
23
|
+
import '../../ui/Tabs/Tabs.styl.js';
|
|
24
|
+
import '../../ui/Page/PageTabs/PageTabs.styl.js';
|
|
25
|
+
import '../../ui/Page/PageColumns/PageColumns.styl.js';
|
|
26
|
+
import '../../ui/Page/SectionHeader/SectionHeader.styl.js';
|
|
27
|
+
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../ui/Table/Table.js';
|
|
28
|
+
import { TextShimmer } from '../../ui/TextShimmer/TextShimmer.js';
|
|
29
|
+
import { TIME_RANGES } from '../../ui/TimeRangeControls/TimeRangeControls.constants.js';
|
|
30
|
+
import S from './DriversComparisonChart.styl.js';
|
|
31
|
+
import { mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData, DRIVER_FORECAST_ID_BASE, INITIAL_VISIBLE_SERIES_COUNT, buildDriversComparisonChartData, formatSeriesImportance } from './driversComparisonChart.helpers.js';
|
|
32
|
+
|
|
33
|
+
const ALL_TIME_RANGE = TIME_RANGES[TIME_RANGES.length - 1];
|
|
34
|
+
function toForecastDataKey(id) {
|
|
35
|
+
const s = String(id);
|
|
36
|
+
return s.startsWith('forecast_') ? s : `forecast_${id}`;
|
|
37
|
+
}
|
|
38
|
+
function DriversComparisonChart({ payload, datasetHistorical = [], loading = false, chartLoading = false, statusHint = null, statusTone = 'muted', runAnalysisHint = false, timeRange: timeRangeProp, onTimeRangeChange, isDarkTheme = false, className, seriesInitKey, }) {
|
|
39
|
+
const [internalTimeRange, setInternalTimeRange] = useState(ALL_TIME_RANGE);
|
|
40
|
+
const [hiddenSeries, setHiddenSeries] = useState(new Set());
|
|
41
|
+
const timeRange = timeRangeProp ?? internalTimeRange;
|
|
42
|
+
const handleTimeRangeChange = useCallback((val) => {
|
|
43
|
+
if (!val)
|
|
44
|
+
return;
|
|
45
|
+
onTimeRangeChange?.(val);
|
|
46
|
+
if (timeRangeProp === undefined) {
|
|
47
|
+
setInternalTimeRange(val);
|
|
48
|
+
}
|
|
49
|
+
}, [onTimeRangeChange, timeRangeProp]);
|
|
50
|
+
const toggleSeries = useCallback((id) => {
|
|
51
|
+
const key = toForecastDataKey(id);
|
|
52
|
+
setHiddenSeries(prev => {
|
|
53
|
+
const next = new Set(prev);
|
|
54
|
+
if (next.has(key))
|
|
55
|
+
next.delete(key);
|
|
56
|
+
else
|
|
57
|
+
next.add(key);
|
|
58
|
+
return next;
|
|
59
|
+
});
|
|
60
|
+
}, []);
|
|
61
|
+
const showSeries = useCallback((id) => {
|
|
62
|
+
const key = toForecastDataKey(id);
|
|
63
|
+
setHiddenSeries(prev => {
|
|
64
|
+
if (!prev.has(key))
|
|
65
|
+
return prev;
|
|
66
|
+
const next = new Set(prev);
|
|
67
|
+
next.delete(key);
|
|
68
|
+
return next;
|
|
69
|
+
});
|
|
70
|
+
}, []);
|
|
71
|
+
const sortedDriversWithData = useMemo(() => {
|
|
72
|
+
const driversList = payload?.drivers ?? [];
|
|
73
|
+
if (!driversList.length)
|
|
74
|
+
return [];
|
|
75
|
+
return [...driversList]
|
|
76
|
+
.filter(d => d.normalized_series &&
|
|
77
|
+
Object.keys(d.normalized_series).some(key => d.normalized_series[key] != null))
|
|
78
|
+
.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
79
|
+
}, [payload?.drivers]);
|
|
80
|
+
const mergedChartData = useMemo(() => mergeBacktestsChartData(payload), [payload]);
|
|
81
|
+
const mergedWithHistorical = useMemo(() => mergeDatasetHistoricalWithBacktestsChartData(datasetHistorical, mergedChartData), [datasetHistorical, mergedChartData]);
|
|
82
|
+
const chartForecastData = useMemo(() => {
|
|
83
|
+
if (!payload?.target?.normalized_series)
|
|
84
|
+
return [];
|
|
85
|
+
return sortedDriversWithData.map((driver, idx) => ({
|
|
86
|
+
id: DRIVER_FORECAST_ID_BASE + idx,
|
|
87
|
+
name: driver.name || String(driver.id),
|
|
88
|
+
color: getForecastColor(idx + 1),
|
|
89
|
+
}));
|
|
90
|
+
}, [payload?.target?.normalized_series, sortedDriversWithData]);
|
|
91
|
+
const tableSeriesRows = useMemo(() => {
|
|
92
|
+
if (sortedDriversWithData.length === 0)
|
|
93
|
+
return [];
|
|
94
|
+
return sortedDriversWithData.map((driver, idx) => {
|
|
95
|
+
const raw = driver.importance;
|
|
96
|
+
const importance = typeof raw === 'number' && Number.isFinite(raw) ? raw : null;
|
|
97
|
+
return {
|
|
98
|
+
id: DRIVER_FORECAST_ID_BASE + idx,
|
|
99
|
+
name: driver.name || String(driver.id),
|
|
100
|
+
color: getForecastColor(idx + 1),
|
|
101
|
+
importance,
|
|
102
|
+
lag: driver.lag,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}, [sortedDriversWithData]);
|
|
106
|
+
const seriesInitKeyResolved = seriesInitKey ?? `${tableSeriesRows.map(r => r.id).join(',') || 'none'}`;
|
|
107
|
+
const backtestsSeriesInitKeyRef = useRef('');
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (tableSeriesRows.length === 0)
|
|
110
|
+
return;
|
|
111
|
+
const initKey = seriesInitKeyResolved;
|
|
112
|
+
if (backtestsSeriesInitKeyRef.current === initKey)
|
|
113
|
+
return;
|
|
114
|
+
backtestsSeriesInitKeyRef.current = initKey;
|
|
115
|
+
setHiddenSeries(() => {
|
|
116
|
+
const next = new Set();
|
|
117
|
+
tableSeriesRows.forEach((row, idx) => {
|
|
118
|
+
const key = toForecastDataKey(row.id);
|
|
119
|
+
if (idx >= INITIAL_VISIBLE_SERIES_COUNT) {
|
|
120
|
+
next.add(key);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return next;
|
|
124
|
+
});
|
|
125
|
+
}, [seriesInitKeyResolved, tableSeriesRows]);
|
|
126
|
+
const driversComparisonChartData = useMemo(() => buildDriversComparisonChartData(mergedWithHistorical, datasetHistorical, chartForecastData.map(f => f.id)), [chartForecastData, datasetHistorical, mergedWithHistorical]);
|
|
127
|
+
const showEmptyOverlay = (runAnalysisHint || Boolean(statusHint)) && !loading;
|
|
128
|
+
return (jsxs("div", { className: cn(S.root, className), children: [jsxs("div", { className: cn(S.chartShell, loading && S.chartShellLoading), children: [jsx("div", { className: S.chartSlot, children: jsxs("div", { className: S.chartWithOverlay, children: [jsx("div", { className: cn(S.chartInteractiveLayer, showEmptyOverlay && S.chartInteractiveDimmed), children: jsx(ChartAreaInteractive, { disableHistoricalAnimation: true, disableTimeRangeSelector: true, enableTimeRangeBrush: true, chartContainerClassName: S.chartContainer, chartData: driversComparisonChartData, forecastData: chartForecastData, timeRange: timeRange, onTimeRangeChange: handleTimeRangeChange, pinMonth: undefined, onPinMonthChange: () => { }, isDarkTheme: isDarkTheme, loading: chartLoading, hasCombinedData: mergedWithHistorical.length > 0, forecastLineStyle: "solid", showLegend: false, hiddenSeries: hiddenSeries, toggleLegendSeries: toggleSeries, ensureAnalysisSeriesVisible: showSeries }) }), showEmptyOverlay && (jsx("div", { className: S.chartEmptyOverlay, role: "status", "aria-live": "polite", children: jsx("div", { className: S.chartEmptyBlurb, children: jsx(ChartEmptyState, { variant: "inline", hint: runAnalysisHint
|
|
129
|
+
? 'Run a completed analysis to load the drivers comparison chart for an analysis.'
|
|
130
|
+
: undefined, status: statusHint ?? undefined, statusTone: statusTone }) }) }))] }) }), loading && (jsx("div", { className: S.loadingLayer, "aria-busy": "true", "aria-live": "polite", children: jsx("div", { className: S.loadingMessage, children: jsx(TextShimmer, { as: "span", className: S.loadingText, children: "Loading drivers comparison\u2026" }) }) }))] }), jsx("div", { className: S.seriesSection, children: tableSeriesRows.length === 0 ? (jsx("div", { className: S.seriesEmptyWrap, children: jsx("div", { className: S.chartEmptyBlurb, children: jsx(ChartEmptyState, { variant: "inline", align: "center", status: "No series" }) }) })) : (jsx("div", { className: S.seriesTableWrapper, children: jsx(PageXScroll, { size: "md", fullWidth: true, innerClassName: S.seriesTableContainer, scrollbarClassName: S.seriesScrollbar, children: jsxs(Table, { withBackground: true, withPaddings: true, className: S.seriesTable, children: [jsx(TableHeader, { children: jsxs(TableRow, { children: [jsx(TableHead, { className: S.seriesColSeries, children: "Driver name" }), jsx(TableHead, { children: "Importance" }), jsx(TableHead, { children: "Lag" })] }) }), jsx(TableBody, { children: tableSeriesRows.map(row => {
|
|
131
|
+
const dataKey = toForecastDataKey(row.id);
|
|
132
|
+
const hidden = hiddenSeries.has(dataKey);
|
|
133
|
+
return (jsxs(TableRow, { className: cn(hidden && S.rowHidden), onClick: () => toggleSeries(row.id), children: [jsx(TableCell, { children: jsxs("span", { className: S.seriesLabel, children: [jsx("span", { className: S.colorSwatch, style: {
|
|
134
|
+
backgroundColor: row.color,
|
|
135
|
+
} }), row.name ?? String(row.id)] }) }), jsx(TableCell, { children: formatSeriesImportance(row.importance) }), jsx(TableCell, { children: row.lag ?? '—' })] }, row.id));
|
|
136
|
+
}) })] }) }) })) })] }));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { DriversComparisonChart };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import styleInject from 'style-inject';
|
|
2
|
+
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.DriversComparisonChart_root__GLnlF{display:flex;flex-direction:column;gap:var(--p-4);width:100%}.DriversComparisonChart_chartShell__0AzAz{position:relative;width:100%}.DriversComparisonChart_chartShellLoading__wTLod .DriversComparisonChart_chartSlot__WKe6N{opacity:.3;pointer-events:none}.DriversComparisonChart_chartShellLoading__wTLod .recharts-line-dots{display:none}.DriversComparisonChart_chartSlot__WKe6N{width:100%}.DriversComparisonChart_loadingLayer__HlEmA{inset:0;pointer-events:none;position:absolute;z-index:10}.DriversComparisonChart_loadingMessage__Li3S7{color:var(--foreground);display:block;left:0;position:absolute;text-align:center;top:calc(50% - 4em);width:100%;z-index:1}.DriversComparisonChart_loadingText__0y3Ms{backdrop-filter:blur(10px);border-radius:var(--p-4);box-shadow:0 0 0 2px var(--page-color);color:var(--foreground);font-size:16px;font-weight:500;padding:0 var(--p-3)}.DriversComparisonChart_chartWithOverlay__SMToK{position:relative;width:100%}.DriversComparisonChart_chartInteractiveLayer__0aquh{transition:opacity .2s ease-out;width:100%}.DriversComparisonChart_chartInteractiveDimmed__us1dv{opacity:.3;pointer-events:none}.DriversComparisonChart_chartInteractiveDimmed__us1dv .recharts-line-dots{display:none}.DriversComparisonChart_chartEmptyOverlay__eXqzk{align-items:center;display:flex;inset:0;justify-content:center;padding:var(--p-4);pointer-events:none;position:absolute;z-index:6}.DriversComparisonChart_chartEmptyBlurb__Tjy9E{max-width:42rem;padding:0 var(--p-3)}.DriversComparisonChart_seriesEmptyWrap__RyRs5{display:flex;justify-content:center;padding:var(--p-8) var(--p-4)}.DriversComparisonChart_seriesSection__yrf4I{width:100%}.DriversComparisonChart_seriesTableWrapper__oredC{margin:0 calc(var(--page-x-padding)*-1);position:relative}.DriversComparisonChart_seriesTableContainer__dhfXK{overflow-x:auto;position:relative;width:100%}.DriversComparisonChart_seriesScrollbar__2jgk6{bottom:calc(var(--p-7)*-1 + 2px)!important}.DriversComparisonChart_seriesTable__NybPR{max-width:100%;table-layout:auto;width:100%}.DriversComparisonChart_seriesTable__NybPR tr{cursor:pointer;position:relative}.DriversComparisonChart_seriesTable__NybPR td:not(:first-child),.DriversComparisonChart_seriesTable__NybPR th:not(:first-child){text-align:right}.DriversComparisonChart_seriesColSeries__--0mj{text-align:left;vertical-align:middle}.DriversComparisonChart_rowHidden__Fwhna{opacity:.4}.DriversComparisonChart_colorSwatch__o3ZVr{border-radius:2px;display:inline-flex;flex-shrink:0;height:10px;margin-right:var(--p-2);width:10px}.DriversComparisonChart_seriesLabel__Aidof{display:-webkit-box;-webkit-line-clamp:3;max-width:60vw;min-width:0;padding:var(--p-2) 0;-webkit-box-orient:vertical;white-space:break-spaces}.DriversComparisonChart_chartContainer__1eM-O{margin-left:calc(var(--page-x-padding)*-1 + 26px);max-width:calc(100% + 60px);transition:opacity .3s ease-out;width:calc(100% + 60px)}.DriversComparisonChart_chartContainer__1eM-O .recharts-yAxis-tick-labels{display:none}@media (max-width:768px){.DriversComparisonChart_chartContainer__1eM-O{margin-left:calc(var(--page-x-padding)*-1 + 10px);width:calc(100% + 30px)}}";
|
|
4
|
+
var S = {"root":"DriversComparisonChart_root__GLnlF","chartShell":"DriversComparisonChart_chartShell__0AzAz","chartShellLoading":"DriversComparisonChart_chartShellLoading__wTLod","chartSlot":"DriversComparisonChart_chartSlot__WKe6N","loadingLayer":"DriversComparisonChart_loadingLayer__HlEmA","loadingMessage":"DriversComparisonChart_loadingMessage__Li3S7","loadingText":"DriversComparisonChart_loadingText__0y3Ms","chartWithOverlay":"DriversComparisonChart_chartWithOverlay__SMToK","chartInteractiveLayer":"DriversComparisonChart_chartInteractiveLayer__0aquh","chartInteractiveDimmed":"DriversComparisonChart_chartInteractiveDimmed__us1dv","chartEmptyOverlay":"DriversComparisonChart_chartEmptyOverlay__eXqzk","chartEmptyBlurb":"DriversComparisonChart_chartEmptyBlurb__Tjy9E","seriesEmptyWrap":"DriversComparisonChart_seriesEmptyWrap__RyRs5","seriesSection":"DriversComparisonChart_seriesSection__yrf4I","seriesTableWrapper":"DriversComparisonChart_seriesTableWrapper__oredC","seriesTableContainer":"DriversComparisonChart_seriesTableContainer__dhfXK","seriesScrollbar":"DriversComparisonChart_seriesScrollbar__2jgk6","seriesTable":"DriversComparisonChart_seriesTable__NybPR","seriesColSeries":"DriversComparisonChart_seriesColSeries__--0mj","rowHidden":"DriversComparisonChart_rowHidden__Fwhna","colorSwatch":"DriversComparisonChart_colorSwatch__o3ZVr","seriesLabel":"DriversComparisonChart_seriesLabel__Aidof","chartContainer":"DriversComparisonChart_chartContainer__1eM-O"};
|
|
5
|
+
styleInject(css_248z);
|
|
6
|
+
|
|
7
|
+
export { S as default };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { normalizeToMonthStart, getPreviousMonth, getNextMonth } from '../../../utils/chartConnectionPoint.js';
|
|
2
|
+
|
|
3
|
+
const DRIVER_FORECAST_ID_BASE = 8_000_000;
|
|
4
|
+
const INITIAL_VISIBLE_SERIES_COUNT = 3;
|
|
5
|
+
/** Months of historical context before the earliest forecast month (inclusive). */
|
|
6
|
+
const DRIVER_COMPARISON_CHART_LEAD_MONTHS = 6;
|
|
7
|
+
/** Ignore leading months where drivers are numerically zero (API often fills 0; lines look empty until real signal). */
|
|
8
|
+
const DRIVER_FORECAST_NONZERO_EPS = 1e-9;
|
|
9
|
+
function formatSeriesImportance(value) {
|
|
10
|
+
if (value === null)
|
|
11
|
+
return '—';
|
|
12
|
+
return `${value.toFixed(1)}%`;
|
|
13
|
+
}
|
|
14
|
+
function mergeBacktestsChartData(payload) {
|
|
15
|
+
if (!payload?.target?.normalized_series)
|
|
16
|
+
return [];
|
|
17
|
+
const norm = (d) => normalizeToMonthStart(d);
|
|
18
|
+
const map = new Map();
|
|
19
|
+
const targetSeries = payload.target.normalized_series;
|
|
20
|
+
Object.entries(targetSeries).forEach(([dateStr, val]) => {
|
|
21
|
+
if (val === null || val === undefined)
|
|
22
|
+
return;
|
|
23
|
+
const k = norm(dateStr);
|
|
24
|
+
const existing = map.get(k) ?? { date: k };
|
|
25
|
+
existing.historical = val;
|
|
26
|
+
map.set(k, existing);
|
|
27
|
+
});
|
|
28
|
+
if (payload.drivers?.length) {
|
|
29
|
+
const sorted = [...payload.drivers]
|
|
30
|
+
.filter(d => d.normalized_series &&
|
|
31
|
+
Object.keys(d.normalized_series).some(key => d.normalized_series[key] != null))
|
|
32
|
+
.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
33
|
+
sorted.forEach((driver, idx) => {
|
|
34
|
+
const fid = DRIVER_FORECAST_ID_BASE + idx;
|
|
35
|
+
const series = driver.normalized_series;
|
|
36
|
+
Object.entries(series).forEach(([dateStr, val]) => {
|
|
37
|
+
if (val === null || val === undefined)
|
|
38
|
+
return;
|
|
39
|
+
const k = norm(dateStr);
|
|
40
|
+
const existing = map.get(k) ?? { date: k };
|
|
41
|
+
existing[`forecast_${fid}`] = val;
|
|
42
|
+
map.set(k, existing);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return Array.from(map.values()).sort((a, b) => a.date.localeCompare(b.date));
|
|
47
|
+
}
|
|
48
|
+
/** While drivers comparison is not loaded: dataset historical only. After load: normalized historical from target.normalized_series (see mergeBacktestsChartData) replaces it. */
|
|
49
|
+
function mergeDatasetHistoricalWithBacktestsChartData(datasetHistorical, backtestsMerged) {
|
|
50
|
+
if (backtestsMerged.length === 0) {
|
|
51
|
+
if (datasetHistorical.length === 0)
|
|
52
|
+
return [];
|
|
53
|
+
return datasetHistorical
|
|
54
|
+
.map(p => {
|
|
55
|
+
const k = normalizeToMonthStart(p.date);
|
|
56
|
+
const histVal = p.historical;
|
|
57
|
+
return {
|
|
58
|
+
date: k,
|
|
59
|
+
...(typeof histVal === 'number' ? { historical: histVal } : {}),
|
|
60
|
+
};
|
|
61
|
+
})
|
|
62
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
63
|
+
}
|
|
64
|
+
return backtestsMerged;
|
|
65
|
+
}
|
|
66
|
+
/** Lower-median of ISO month strings (YYYY-MM-01 sorts lexicographically). */
|
|
67
|
+
function medianSortedIsoMonth(months) {
|
|
68
|
+
const s = [...months].sort((a, b) => a.localeCompare(b));
|
|
69
|
+
return s[Math.floor((s.length - 1) / 2)];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* For each driver, first month with materially non-zero value; anchor = median of those months.
|
|
73
|
+
* (Min is too early with outliers; max matched logs where one driver started 2017-12 while most started 2015-01.)
|
|
74
|
+
*/
|
|
75
|
+
function getZoomAnchorMonthFromDriverMaterialStarts(points, forecastIds) {
|
|
76
|
+
const perDriverFirstMaterial = [];
|
|
77
|
+
for (const id of forecastIds) {
|
|
78
|
+
let minForId = null;
|
|
79
|
+
for (const p of points) {
|
|
80
|
+
const v = p[`forecast_${id}`];
|
|
81
|
+
if (typeof v !== 'number' ||
|
|
82
|
+
!Number.isFinite(v) ||
|
|
83
|
+
Math.abs(v) <= DRIVER_FORECAST_NONZERO_EPS) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const d = normalizeToMonthStart(p.date);
|
|
87
|
+
if (minForId === null || d.localeCompare(minForId) < 0)
|
|
88
|
+
minForId = d;
|
|
89
|
+
}
|
|
90
|
+
if (minForId === null) {
|
|
91
|
+
return { anchor: null, perDriverFirstMaterial };
|
|
92
|
+
}
|
|
93
|
+
perDriverFirstMaterial.push(minForId);
|
|
94
|
+
}
|
|
95
|
+
const anchor = medianSortedIsoMonth(perDriverFirstMaterial);
|
|
96
|
+
return { anchor, perDriverFirstMaterial };
|
|
97
|
+
}
|
|
98
|
+
function subtractMonthsFromMonthStart(date, count) {
|
|
99
|
+
let result = date;
|
|
100
|
+
for (let i = 0; i < count; i++) {
|
|
101
|
+
result = getPreviousMonth(result);
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Backtests payload often starts at the same month for target + drivers, so xMin falls
|
|
107
|
+
* in a range with no rows. Prepend historical-only months from raw dataset series
|
|
108
|
+
* (scaled to the first normalized point) so the lead-in shows the target line alone.
|
|
109
|
+
*/
|
|
110
|
+
function prependHistoricalLeadFromDataset(points, datasetHistorical, xMin) {
|
|
111
|
+
if (points.length === 0)
|
|
112
|
+
return points;
|
|
113
|
+
const firstMonth = normalizeToMonthStart(points[0].date);
|
|
114
|
+
if (xMin.localeCompare(firstMonth) >= 0)
|
|
115
|
+
return points;
|
|
116
|
+
const rawByMonth = new Map();
|
|
117
|
+
for (const p of datasetHistorical) {
|
|
118
|
+
const k = normalizeToMonthStart(p.date);
|
|
119
|
+
const v = p.historical;
|
|
120
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
121
|
+
rawByMonth.set(k, v);
|
|
122
|
+
}
|
|
123
|
+
const histFirst = points[0].historical;
|
|
124
|
+
const rawFirst = rawByMonth.get(firstMonth);
|
|
125
|
+
if (typeof histFirst !== 'number' ||
|
|
126
|
+
!Number.isFinite(histFirst) ||
|
|
127
|
+
typeof rawFirst !== 'number' ||
|
|
128
|
+
!Number.isFinite(rawFirst) ||
|
|
129
|
+
Math.abs(rawFirst) <= 1e-15) {
|
|
130
|
+
return points;
|
|
131
|
+
}
|
|
132
|
+
const lead = [];
|
|
133
|
+
let m = xMin;
|
|
134
|
+
while (m.localeCompare(firstMonth) < 0) {
|
|
135
|
+
const rawM = rawByMonth.get(m);
|
|
136
|
+
if (typeof rawM === 'number' && Number.isFinite(rawM)) {
|
|
137
|
+
lead.push({
|
|
138
|
+
date: m,
|
|
139
|
+
historical: histFirst * (rawM / rawFirst),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
m = getNextMonth(m);
|
|
143
|
+
}
|
|
144
|
+
return [...lead, ...points];
|
|
145
|
+
}
|
|
146
|
+
function buildDriversComparisonChartData(mergedWithHistorical, datasetHistorical, forecastIds) {
|
|
147
|
+
if (mergedWithHistorical.length === 0)
|
|
148
|
+
return mergedWithHistorical;
|
|
149
|
+
if (forecastIds.length === 0)
|
|
150
|
+
return mergedWithHistorical;
|
|
151
|
+
const { anchor: anchorMonth } = getZoomAnchorMonthFromDriverMaterialStarts(mergedWithHistorical, forecastIds);
|
|
152
|
+
if (anchorMonth === null)
|
|
153
|
+
return mergedWithHistorical;
|
|
154
|
+
const xMin = subtractMonthsFromMonthStart(anchorMonth, DRIVER_COMPARISON_CHART_LEAD_MONTHS);
|
|
155
|
+
const withLead = prependHistoricalLeadFromDataset(mergedWithHistorical, datasetHistorical, xMin);
|
|
156
|
+
return withLead.filter(p => normalizeToMonthStart(p.date) >= xMin);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { DRIVER_COMPARISON_CHART_LEAD_MONTHS, DRIVER_FORECAST_ID_BASE, INITIAL_VISIBLE_SERIES_COUNT, buildDriversComparisonChartData, formatSeriesImportance, getZoomAnchorMonthFromDriverMaterialStarts, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData, prependHistoricalLeadFromDataset, subtractMonthsFromMonthStart };
|
package/dist/esm/index.js
CHANGED
|
@@ -104,6 +104,8 @@ export { DriverMap } from './components/widgets/DriverMap/DriverMap.js';
|
|
|
104
104
|
export { getCategoryIcon } from './components/widgets/DriverMap/driverCategoryIcon.js';
|
|
105
105
|
export { getDriverImportance, getHighestImportanceDriver } from './components/widgets/DriverMap/driverMapSelection.js';
|
|
106
106
|
export { geographicCoordinates, geographicToSVG, getContinentFromRegion, getPreciseCoordinates, getResponsiveCoordinates, svgToPercentage } from './components/widgets/DriverMap/driverMapGeography.js';
|
|
107
|
+
export { DriversComparisonChart } from './components/widgets/DriversComparisonChart/DriversComparisonChart.js';
|
|
108
|
+
export { DRIVER_COMPARISON_CHART_LEAD_MONTHS, DRIVER_FORECAST_ID_BASE, INITIAL_VISIBLE_SERIES_COUNT, buildDriversComparisonChartData, formatSeriesImportance, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData } from './components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js';
|
|
107
109
|
export { SybilionAppHeader } from './components/widgets/SybilionAppHeader/SybilionAppHeader.js';
|
|
108
110
|
export { SybilionAuthLayout } from './components/widgets/SybilionAuthLayout/SybilionAuthLayout.js';
|
|
109
111
|
export { SybilionAuthHeadline } from './components/widgets/SybilionAuthLayout/SybilionAuthHeadline.js';
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import type { ChatPreset } from '#uilib/components/ui/Chat/Chat.types';
|
|
2
|
+
export type ChatEmptyStateContext = {
|
|
3
|
+
submitPreset: (preset: ChatPreset) => void | Promise<void>;
|
|
4
|
+
};
|
|
1
5
|
export interface ChatEmptyStateProps {
|
|
2
6
|
icon?: React.ReactNode;
|
|
3
7
|
title?: string;
|
|
4
8
|
description?: string;
|
|
5
|
-
/** Extra block below description (
|
|
9
|
+
/** Extra block below description (resolved before render in `ChatEmptyState`). */
|
|
6
10
|
additionalContent?: React.ReactNode;
|
|
7
11
|
}
|
|
12
|
+
/** Passed to `ChatSheet` / `useChatPanelChromeModel`; function form resolved before render. */
|
|
13
|
+
export interface ChatEmptyStateConfig extends Omit<ChatEmptyStateProps, 'additionalContent'> {
|
|
14
|
+
additionalContent?: React.ReactNode | ((ctx: ChatEmptyStateContext) => React.ReactNode);
|
|
15
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ChatPreset, type ScriptCompletePayload } from '#uilib/components/ui/Chat/Chat.types';
|
|
2
2
|
import type { ChatChromeProps } from '../ChatChrome';
|
|
3
3
|
import type { ChatAttachmentDropItem } from '../ChatChrome/ChatChrome.types';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ChatEmptyStateConfig } from '../ChatEmptyState/ChatEmptyState.types';
|
|
5
5
|
export type UseChatPanelChromeModelInput = {
|
|
6
6
|
/** When true, skip sidebar chat slot, URL `?chat=`, and portal behavior (e.g. page main content). */
|
|
7
7
|
embedAsPage: boolean;
|
|
@@ -16,7 +16,7 @@ export type UseChatPanelChromeModelInput = {
|
|
|
16
16
|
/** Renders `[CHART]` tokens in assistant messages. */
|
|
17
17
|
renderMessageChart?: () => React.ReactNode;
|
|
18
18
|
/** Forwarded to `ChatChrome` when the thread is empty. */
|
|
19
|
-
emptyState?:
|
|
19
|
+
emptyState?: ChatEmptyStateConfig;
|
|
20
20
|
/** MIME types / extensions for text-only chat attachments (filtered by uilib allowlist). */
|
|
21
21
|
allowedAttachments?: readonly string[];
|
|
22
22
|
/** When true, PDF drops are accepted and parsed to plain text. */
|
|
@@ -12,6 +12,7 @@ export type { UseChatPanelChromeModelInput, UseChatPanelChromeModelResult, } fro
|
|
|
12
12
|
export { ChatMessage } from './ChatMessage';
|
|
13
13
|
export { ChatPrompt } from './ChatPrompt';
|
|
14
14
|
export { ChatPresets } from './ChatPresets';
|
|
15
|
+
export type { ChatEmptyStateConfig, ChatEmptyStateContext, ChatEmptyStateProps, } from './ChatEmptyState/ChatEmptyState.types';
|
|
15
16
|
export type { Chat as ChatType, ChatAttachmentDropItem, ChatSendMessagePayload, ChatProps, ChatPreset as ChatPresetType, Message, UserTextFileAttachment, } from './Chat.types';
|
|
16
17
|
export { MessageRole } from './Chat.types';
|
|
17
18
|
export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
|
package/dist/esm/types/src/components/widgets/DriversComparisonChart/DriversComparisonChart.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
|
|
2
|
+
import type { BacktestsComponentPayload } from '@sybilion/platform-sdk';
|
|
3
|
+
export type DriversComparisonChartProps = {
|
|
4
|
+
payload: BacktestsComponentPayload | null;
|
|
5
|
+
datasetHistorical?: ChartDataPoint[];
|
|
6
|
+
loading?: boolean;
|
|
7
|
+
chartLoading?: boolean;
|
|
8
|
+
statusHint?: string | null;
|
|
9
|
+
statusTone?: 'destructive' | 'muted';
|
|
10
|
+
runAnalysisHint?: boolean;
|
|
11
|
+
timeRange?: string;
|
|
12
|
+
onTimeRangeChange?: (range: string) => void;
|
|
13
|
+
isDarkTheme?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
/** Resets visible series when this key changes (e.g. selected analysis id). */
|
|
16
|
+
seriesInitKey?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function DriversComparisonChart({ payload, datasetHistorical, loading, chartLoading, statusHint, statusTone, runAnalysisHint, timeRange: timeRangeProp, onTimeRangeChange, isDarkTheme, className, seriesInitKey, }: DriversComparisonChartProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
|
|
2
|
+
import type { BacktestsComponentPayload } from '@sybilion/platform-sdk';
|
|
3
|
+
export declare const DRIVER_FORECAST_ID_BASE = 8000000;
|
|
4
|
+
export declare const INITIAL_VISIBLE_SERIES_COUNT = 3;
|
|
5
|
+
/** Months of historical context before the earliest forecast month (inclusive). */
|
|
6
|
+
export declare const DRIVER_COMPARISON_CHART_LEAD_MONTHS = 6;
|
|
7
|
+
export declare function formatSeriesImportance(value: number | null): string;
|
|
8
|
+
export declare function mergeBacktestsChartData(payload: BacktestsComponentPayload | null): ChartDataPoint[];
|
|
9
|
+
/** While drivers comparison is not loaded: dataset historical only. After load: normalized historical from target.normalized_series (see mergeBacktestsChartData) replaces it. */
|
|
10
|
+
export declare function mergeDatasetHistoricalWithBacktestsChartData(datasetHistorical: ChartDataPoint[], backtestsMerged: ChartDataPoint[]): ChartDataPoint[];
|
|
11
|
+
/**
|
|
12
|
+
* For each driver, first month with materially non-zero value; anchor = median of those months.
|
|
13
|
+
* (Min is too early with outliers; max matched logs where one driver started 2017-12 while most started 2015-01.)
|
|
14
|
+
*/
|
|
15
|
+
export declare function getZoomAnchorMonthFromDriverMaterialStarts(points: ChartDataPoint[], forecastIds: number[]): {
|
|
16
|
+
anchor: string | null;
|
|
17
|
+
perDriverFirstMaterial: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function subtractMonthsFromMonthStart(date: string, count: number): string;
|
|
20
|
+
/**
|
|
21
|
+
* Backtests payload often starts at the same month for target + drivers, so xMin falls
|
|
22
|
+
* in a range with no rows. Prepend historical-only months from raw dataset series
|
|
23
|
+
* (scaled to the first normalized point) so the lead-in shows the target line alone.
|
|
24
|
+
*/
|
|
25
|
+
export declare function prependHistoricalLeadFromDataset(points: ChartDataPoint[], datasetHistorical: ChartDataPoint[], xMin: string): ChartDataPoint[];
|
|
26
|
+
export declare function buildDriversComparisonChartData(mergedWithHistorical: ChartDataPoint[], datasetHistorical: ChartDataPoint[], forecastIds: number[]): ChartDataPoint[];
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { DriversComparisonChart, type DriversComparisonChartProps, } from './DriversComparisonChart';
|
|
2
|
+
export { buildDriversComparisonChartData, DRIVER_COMPARISON_CHART_LEAD_MONTHS, DRIVER_FORECAST_ID_BASE, formatSeriesImportance, INITIAL_VISIBLE_SERIES_COUNT, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData, } from './driversComparisonChart.helpers';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function DriversComparisonChartPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -68,6 +68,7 @@ export * from './components/ui/WorkspaceAppSwitcher';
|
|
|
68
68
|
export * from './components/widgets/SidebarDatasetsItemsGrouped';
|
|
69
69
|
export * from './components/widgets/DriverCard';
|
|
70
70
|
export * from './components/widgets/DriverMap';
|
|
71
|
+
export * from './components/widgets/DriversComparisonChart';
|
|
71
72
|
export * from './components/widgets/SybilionAppHeader';
|
|
72
73
|
export * from './components/widgets/SybilionAuthLayout';
|
|
73
74
|
export * from './components/widgets/SybilionSignInPanel';
|
|
@@ -101,5 +101,13 @@ function getPreviousMonth(date) {
|
|
|
101
101
|
d.setMonth(d.getMonth() - 1);
|
|
102
102
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Get the next month date (first day of next month)
|
|
106
|
+
*/
|
|
107
|
+
function getNextMonth(date) {
|
|
108
|
+
const d = new Date(date);
|
|
109
|
+
d.setMonth(d.getMonth() + 1);
|
|
110
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;
|
|
111
|
+
}
|
|
104
112
|
|
|
105
|
-
export { ensureChartForecastBridge, findLastValidHistoricalChartPoint, getPreviousMonth, normalizeToMonthStart };
|
|
113
|
+
export { ensureChartForecastBridge, findLastValidHistoricalChartPoint, getNextMonth, getPreviousMonth, normalizeToMonthStart };
|
package/package.json
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
import type { ChatPreset } from '#uilib/components/ui/Chat/Chat.types';
|
|
2
|
+
|
|
3
|
+
export type ChatEmptyStateContext = {
|
|
4
|
+
submitPreset: (preset: ChatPreset) => void | Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
|
|
1
7
|
export interface ChatEmptyStateProps {
|
|
2
8
|
icon?: React.ReactNode;
|
|
3
9
|
title?: string;
|
|
4
10
|
description?: string;
|
|
5
|
-
/** Extra block below description (
|
|
11
|
+
/** Extra block below description (resolved before render in `ChatEmptyState`). */
|
|
6
12
|
additionalContent?: React.ReactNode;
|
|
7
13
|
}
|
|
14
|
+
|
|
15
|
+
/** Passed to `ChatSheet` / `useChatPanelChromeModel`; function form resolved before render. */
|
|
16
|
+
export interface ChatEmptyStateConfig extends Omit<
|
|
17
|
+
ChatEmptyStateProps,
|
|
18
|
+
'additionalContent'
|
|
19
|
+
> {
|
|
20
|
+
additionalContent?:
|
|
21
|
+
| React.ReactNode
|
|
22
|
+
| ((ctx: ChatEmptyStateContext) => React.ReactNode);
|
|
23
|
+
}
|
|
@@ -45,6 +45,7 @@ import { useSidebar } from '../../Sidebar/Sidebar';
|
|
|
45
45
|
import { Chat } from '../Chat';
|
|
46
46
|
import type { ChatChromeProps } from '../ChatChrome';
|
|
47
47
|
import type { ChatAttachmentDropItem } from '../ChatChrome/ChatChrome.types';
|
|
48
|
+
import type { ChatEmptyStateConfig } from '../ChatEmptyState/ChatEmptyState.types';
|
|
48
49
|
import type { ChatEmptyStateProps } from '../ChatEmptyState/ChatEmptyState.types';
|
|
49
50
|
|
|
50
51
|
export type UseChatPanelChromeModelInput = {
|
|
@@ -61,7 +62,7 @@ export type UseChatPanelChromeModelInput = {
|
|
|
61
62
|
/** Renders `[CHART]` tokens in assistant messages. */
|
|
62
63
|
renderMessageChart?: () => React.ReactNode;
|
|
63
64
|
/** Forwarded to `ChatChrome` when the thread is empty. */
|
|
64
|
-
emptyState?:
|
|
65
|
+
emptyState?: ChatEmptyStateConfig;
|
|
65
66
|
/** MIME types / extensions for text-only chat attachments (filtered by uilib allowlist). */
|
|
66
67
|
allowedAttachments?: readonly string[];
|
|
67
68
|
/** When true, PDF drops are accepted and parsed to plain text. */
|
|
@@ -615,7 +616,7 @@ export function useChatPanelChromeModel({
|
|
|
615
616
|
],
|
|
616
617
|
);
|
|
617
618
|
|
|
618
|
-
const submitPreset = async (preset: ChatPreset) => {
|
|
619
|
+
const submitPreset = useCallback(async (preset: ChatPreset) => {
|
|
619
620
|
const script = preset.script;
|
|
620
621
|
const scriptGraph = isPresetScriptGraph(script);
|
|
621
622
|
const hasLinearScript = Array.isArray(script) && script.length > 0;
|
|
@@ -705,7 +706,25 @@ export function useChatPanelChromeModel({
|
|
|
705
706
|
} finally {
|
|
706
707
|
setLocalUiBusy(false);
|
|
707
708
|
}
|
|
708
|
-
}
|
|
709
|
+
}, [
|
|
710
|
+
currentChatId,
|
|
711
|
+
endLocalDemoFlow,
|
|
712
|
+
handlePromptSubmit,
|
|
713
|
+
addMessage,
|
|
714
|
+
presetsWithFreeform,
|
|
715
|
+
]);
|
|
716
|
+
|
|
717
|
+
const resolvedEmptyState = useMemo((): ChatEmptyStateProps | undefined => {
|
|
718
|
+
if (!emptyState) return undefined;
|
|
719
|
+
const { additionalContent, ...rest } = emptyState;
|
|
720
|
+
return {
|
|
721
|
+
...rest,
|
|
722
|
+
additionalContent:
|
|
723
|
+
typeof additionalContent === 'function'
|
|
724
|
+
? additionalContent({ submitPreset })
|
|
725
|
+
: additionalContent,
|
|
726
|
+
};
|
|
727
|
+
}, [emptyState, submitPreset]);
|
|
709
728
|
|
|
710
729
|
const activeScript = currentChatId
|
|
711
730
|
? scriptByChatId[currentChatId]
|
|
@@ -1017,7 +1036,7 @@ export function useChatPanelChromeModel({
|
|
|
1017
1036
|
onPromptSubmit: handlePromptSubmit,
|
|
1018
1037
|
onChatDeleted: endLocalDemoFlow,
|
|
1019
1038
|
promptPrefill: promptLinkPrefill,
|
|
1020
|
-
emptyState,
|
|
1039
|
+
emptyState: resolvedEmptyState,
|
|
1021
1040
|
allowedAttachments,
|
|
1022
1041
|
allowPdfAttachments,
|
|
1023
1042
|
onAttachmentsDropped,
|
|
@@ -25,6 +25,11 @@ export type {
|
|
|
25
25
|
export { ChatMessage } from './ChatMessage';
|
|
26
26
|
export { ChatPrompt } from './ChatPrompt';
|
|
27
27
|
export { ChatPresets } from './ChatPresets';
|
|
28
|
+
export type {
|
|
29
|
+
ChatEmptyStateConfig,
|
|
30
|
+
ChatEmptyStateContext,
|
|
31
|
+
ChatEmptyStateProps,
|
|
32
|
+
} from './ChatEmptyState/ChatEmptyState.types';
|
|
28
33
|
export type {
|
|
29
34
|
Chat as ChatType,
|
|
30
35
|
ChatAttachmentDropItem,
|