@schandlergarcia/sf-web-components 1.7.0 → 1.9.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/components/library/cards/ActionList.d.ts +10 -10
- package/dist/components/library/cards/ActionList.js +2 -3
- package/dist/components/library/cards/ActionList.js.map +1 -1
- package/dist/components/library/cards/ActivityCard.d.ts +18 -5
- package/dist/components/library/cards/ActivityCard.js +3 -4
- package/dist/components/library/cards/ActivityCard.js.map +1 -1
- package/dist/components/library/cards/BaseCard.d.ts +30 -24
- package/dist/components/library/cards/BaseCard.js +2 -3
- package/dist/components/library/cards/BaseCard.js.map +1 -1
- package/dist/components/library/cards/CalloutCard.d.ts +11 -9
- package/dist/components/library/cards/CalloutCard.js +2 -3
- package/dist/components/library/cards/CalloutCard.js.map +1 -1
- package/dist/components/library/cards/ChartCard.d.ts +29 -17
- package/dist/components/library/cards/ChartCard.js +13 -14
- package/dist/components/library/cards/ChartCard.js.map +1 -1
- package/dist/components/library/cards/FeedPanel.d.ts +12 -11
- package/dist/components/library/cards/FeedPanel.js +3 -4
- package/dist/components/library/cards/FeedPanel.js.map +1 -1
- package/dist/components/library/cards/ListCard.d.ts +33 -20
- package/dist/components/library/cards/ListCard.js +35 -35
- package/dist/components/library/cards/ListCard.js.map +1 -1
- package/dist/components/library/cards/MetricCard.d.ts +23 -17
- package/dist/components/library/cards/MetricCard.js +10 -11
- package/dist/components/library/cards/MetricCard.js.map +1 -1
- package/dist/components/library/cards/MetricsStrip.d.ts +11 -11
- package/dist/components/library/cards/MetricsStrip.js +1 -1
- package/dist/components/library/cards/MetricsStrip.js.map +1 -1
- package/dist/components/library/cards/SectionCard.d.ts +17 -12
- package/dist/components/library/cards/SectionCard.js +18 -19
- package/dist/components/library/cards/SectionCard.js.map +1 -1
- package/dist/components/library/cards/SemanticMetricCard.d.ts +15 -20
- package/dist/components/library/cards/SemanticMetricCardWithLoading.d.ts +8 -7
- package/dist/components/library/cards/SemanticTableCard.d.ts +13 -18
- package/dist/components/library/cards/SemanticTableCardWithLoading.d.ts +8 -7
- package/dist/components/library/cards/StatusCard.d.ts +29 -15
- package/dist/components/library/cards/StatusCard.js +16 -17
- package/dist/components/library/cards/StatusCard.js.map +1 -1
- package/dist/components/library/cards/TableCard.d.ts +40 -23
- package/dist/components/library/cards/TableCard.js +59 -59
- package/dist/components/library/cards/TableCard.js.map +1 -1
- package/dist/components/library/cards/WidgetCard.d.ts +19 -11
- package/dist/components/library/cards/WidgetCard.js.map +1 -1
- package/dist/components/library/charts/D3Chart.d.ts +23 -16
- package/dist/components/library/charts/D3Chart.js.map +1 -1
- package/dist/components/library/charts/D3ChartTemplates.d.ts +33 -3
- package/dist/components/library/charts/D3ChartTemplates.js +7 -7
- package/dist/components/library/charts/D3ChartTemplates.js.map +1 -1
- package/dist/components/library/charts/GeoMap.d.ts +81 -18
- package/dist/components/library/charts/GeoMap.js +28 -26
- package/dist/components/library/charts/GeoMap.js.map +1 -1
- package/dist/components/library/chat/ChatBar.d.ts +14 -11
- package/dist/components/library/chat/ChatBar.js +2 -3
- package/dist/components/library/chat/ChatBar.js.map +1 -1
- package/dist/components/library/chat/ChatInput.d.ts +9 -8
- package/dist/components/library/chat/ChatInput.js.map +1 -1
- package/dist/components/library/chat/ChatMessage.d.ts +17 -4
- package/dist/components/library/chat/ChatMessage.js.map +1 -1
- package/dist/components/library/chat/ChatMessageList.d.ts +11 -8
- package/dist/components/library/chat/ChatMessageList.js.map +1 -1
- package/dist/components/library/chat/ChatPanel.d.ts +16 -12
- package/dist/components/library/chat/ChatPanel.js +8 -9
- package/dist/components/library/chat/ChatPanel.js.map +1 -1
- package/dist/components/library/chat/ChatSuggestions.d.ts +5 -4
- package/dist/components/library/chat/ChatSuggestions.js +2 -3
- package/dist/components/library/chat/ChatSuggestions.js.map +1 -1
- package/dist/components/library/chat/ChatToolCall.d.ts +11 -3
- package/dist/components/library/chat/ChatToolCall.js.map +1 -1
- package/dist/components/library/chat/ChatTypingIndicator.d.ts +4 -3
- package/dist/components/library/chat/ChatTypingIndicator.js +2 -3
- package/dist/components/library/chat/ChatTypingIndicator.js.map +1 -1
- package/dist/components/library/chat/ChatWelcome.d.ts +9 -7
- package/dist/components/library/chat/ChatWelcome.js +6 -7
- package/dist/components/library/chat/ChatWelcome.js.map +1 -1
- package/dist/components/library/chat/index.d.ts +10 -0
- package/dist/components/library/chat/useChatState.d.ts +36 -11
- package/dist/components/library/chat/useChatState.js +63 -46
- package/dist/components/library/chat/useChatState.js.map +1 -1
- package/dist/components/library/data/DataModeProvider.d.ts +15 -11
- package/dist/components/library/data/DataModeProvider.js +1 -1
- package/dist/components/library/data/DataModeProvider.js.map +1 -1
- package/dist/components/library/data/DataModeToggle.d.ts +4 -3
- package/dist/components/library/data/DataModeToggle.js +4 -5
- package/dist/components/library/data/DataModeToggle.js.map +1 -1
- package/dist/components/library/data/chartDataProvider.d.ts +41 -3
- package/dist/components/library/data/filterUtils.d.ts +38 -9
- package/dist/components/library/data/filterUtils.js.map +1 -1
- package/dist/components/library/data/useDataSource.d.ts +6 -4
- package/dist/components/library/data/useDataSource.js.map +1 -1
- package/dist/components/library/data/usePageFilters.d.ts +31 -5
- package/dist/components/library/data/usePageFilters.js +6 -2
- package/dist/components/library/data/usePageFilters.js.map +1 -1
- package/dist/components/library/filters/FilterBar.d.ts +18 -8
- package/dist/components/library/filters/FilterBar.js +2 -3
- package/dist/components/library/filters/FilterBar.js.map +1 -1
- package/dist/components/library/filters/SearchFilter.d.ts +7 -6
- package/dist/components/library/filters/SearchFilter.js +2 -3
- package/dist/components/library/filters/SearchFilter.js.map +1 -1
- package/dist/components/library/filters/SelectFilter.d.ts +13 -7
- package/dist/components/library/filters/SelectFilter.js +2 -3
- package/dist/components/library/filters/SelectFilter.js.map +1 -1
- package/dist/components/library/filters/ToggleFilter.d.ts +7 -5
- package/dist/components/library/filters/ToggleFilter.js +2 -3
- package/dist/components/library/filters/ToggleFilter.js.map +1 -1
- package/dist/components/library/forms/FormField.d.ts +10 -8
- package/dist/components/library/forms/FormField.js +3 -4
- package/dist/components/library/forms/FormField.js.map +1 -1
- package/dist/components/library/forms/FormModal.d.ts +23 -14
- package/dist/components/library/forms/FormModal.js.map +1 -1
- package/dist/components/library/forms/FormRenderer.d.ts +29 -9
- package/dist/components/library/forms/FormRenderer.js +6 -7
- package/dist/components/library/forms/FormRenderer.js.map +1 -1
- package/dist/components/library/forms/FormSection.d.ts +10 -8
- package/dist/components/library/forms/FormSection.js +2 -3
- package/dist/components/library/forms/FormSection.js.map +1 -1
- package/dist/components/library/forms/index.d.ts +5 -0
- package/dist/components/library/forms/useFormState.d.ts +23 -15
- package/dist/components/library/forms/useFormState.js +53 -47
- package/dist/components/library/forms/useFormState.js.map +1 -1
- package/dist/components/library/index.d.ts +92 -73
- package/dist/components/library/index.js +25 -25
- package/dist/components/library/index.js.map +1 -1
- package/dist/components/library/layout/PageContainer.d.ts +6 -4
- package/dist/components/library/layout/PageContainer.js +4 -5
- package/dist/components/library/layout/PageContainer.js.map +1 -1
- package/dist/components/library/skeletons/CardSkeleton.d.ts +5 -4
- package/dist/components/library/skeletons/CardSkeleton.js +2 -3
- package/dist/components/library/skeletons/CardSkeleton.js.map +1 -1
- package/dist/components/library/theme/AppThemeProvider.d.ts +13 -50
- package/dist/components/library/theme/AppThemeProvider.js.map +1 -1
- package/dist/components/library/theme/tokens.d.ts +45 -44
- package/dist/components/library/theme/tokens.js.map +1 -1
- package/package.json +4 -1
- package/src/components/library/cards/{ActionList.jsx → ActionList.tsx} +13 -9
- package/src/components/library/cards/{ActivityCard.jsx → ActivityCard.tsx} +33 -4
- package/src/components/library/cards/{BaseCard.jsx → BaseCard.tsx} +33 -6
- package/src/components/library/cards/{CalloutCard.jsx → CalloutCard.tsx} +12 -10
- package/src/components/library/cards/{ChartCard.jsx → ChartCard.tsx} +32 -6
- package/src/components/library/cards/{FeedPanel.jsx → FeedPanel.tsx} +13 -2
- package/src/components/library/cards/{ListCard.jsx → ListCard.tsx} +43 -7
- package/src/components/library/cards/{MetricCard.jsx → MetricCard.tsx} +25 -6
- package/src/components/library/cards/{MetricsStrip.jsx → MetricsStrip.tsx} +22 -12
- package/src/components/library/cards/{SectionCard.jsx → SectionCard.tsx} +27 -8
- package/src/components/library/cards/{SemanticMetricCard.jsx → SemanticMetricCard.tsx} +18 -6
- package/src/components/library/cards/{SemanticMetricCardWithLoading.jsx → SemanticMetricCardWithLoading.tsx} +9 -3
- package/src/components/library/cards/{SemanticTableCard.jsx → SemanticTableCard.tsx} +16 -5
- package/src/components/library/cards/{SemanticTableCardWithLoading.jsx → SemanticTableCardWithLoading.tsx} +9 -5
- package/src/components/library/cards/{StatusCard.jsx → StatusCard.tsx} +61 -12
- package/src/components/library/cards/{TableCard.jsx → TableCard.tsx} +51 -12
- package/src/components/library/cards/{WidgetCard.jsx → WidgetCard.tsx} +28 -5
- package/src/components/library/charts/{D3Chart.jsx → D3Chart.tsx} +27 -7
- package/src/components/library/charts/{D3ChartTemplates.jsx → D3ChartTemplates.tsx} +60 -28
- package/src/components/library/charts/{GeoMap.jsx → GeoMap.tsx} +106 -17
- package/src/components/library/chat/{ChatBar.jsx → ChatBar.tsx} +19 -8
- package/src/components/library/chat/{ChatInput.jsx → ChatInput.tsx} +13 -11
- package/src/components/library/chat/{ChatMessage.jsx → ChatMessage.tsx} +22 -9
- package/src/components/library/chat/{ChatMessageList.jsx → ChatMessageList.tsx} +13 -11
- package/src/components/library/chat/{ChatPanel.jsx → ChatPanel.tsx} +16 -13
- package/src/components/library/chat/{ChatSuggestions.jsx → ChatSuggestions.tsx} +6 -5
- package/src/components/library/chat/{ChatToolCall.jsx → ChatToolCall.tsx} +14 -4
- package/src/components/library/chat/{ChatTypingIndicator.jsx → ChatTypingIndicator.tsx} +5 -2
- package/src/components/library/chat/{ChatWelcome.jsx → ChatWelcome.tsx} +9 -7
- package/src/components/library/chat/index.tsx +26 -0
- package/src/components/library/chat/useChatState.tsx +181 -0
- package/src/components/library/data/{DataModeProvider.jsx → DataModeProvider.tsx} +25 -8
- package/src/components/library/data/{DataModeToggle.jsx → DataModeToggle.tsx} +5 -2
- package/src/components/library/data/{chartDataProvider.jsx → chartDataProvider.tsx} +49 -5
- package/src/components/library/data/{filterUtils.jsx → filterUtils.tsx} +58 -12
- package/src/components/library/data/{useDataSource.jsx → useDataSource.tsx} +9 -2
- package/src/components/library/data/{usePageFilters.jsx → usePageFilters.tsx} +49 -9
- package/src/components/library/filters/{FilterBar.jsx → FilterBar.tsx} +21 -11
- package/src/components/library/filters/{SearchFilter.jsx → SearchFilter.tsx} +8 -2
- package/src/components/library/filters/{SelectFilter.jsx → SelectFilter.tsx} +15 -8
- package/src/components/library/filters/{ToggleFilter.jsx → ToggleFilter.tsx} +7 -6
- package/src/components/library/forms/{FormField.jsx → FormField.tsx} +91 -45
- package/src/components/library/forms/{FormModal.jsx → FormModal.tsx} +21 -20
- package/src/components/library/forms/{FormRenderer.jsx → FormRenderer.tsx} +32 -10
- package/src/components/library/forms/{FormSection.jsx → FormSection.tsx} +13 -7
- package/src/components/library/forms/index.tsx +11 -0
- package/src/components/library/forms/{useFormState.jsx → useFormState.tsx} +43 -23
- package/src/components/library/{index.jsx → index.ts} +14 -14
- package/src/components/library/layout/{PageContainer.jsx → PageContainer.tsx} +6 -3
- package/src/components/library/skeletons/{CardSkeleton.jsx → CardSkeleton.tsx} +5 -4
- package/src/components/library/theme/{AppThemeProvider.jsx → AppThemeProvider.tsx} +20 -7
- package/src/components/library/theme/{tokens.jsx → tokens.tsx} +37 -3
- package/src/components/library/chat/index.jsx +0 -10
- package/src/components/library/chat/useChatState.jsx +0 -130
- package/src/components/library/forms/index.jsx +0 -5
- /package/src/components/library/filters/{index.jsx → index.ts} +0 -0
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
+
interface Dimensions {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface D3ChartProps {
|
|
9
|
+
data: unknown;
|
|
10
|
+
renderChart?: (svg: SVGSVGElement, data: unknown, dims: Dimensions, options: Record<string, unknown>) => void;
|
|
11
|
+
options?: Record<string, unknown>;
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
responsive?: boolean;
|
|
15
|
+
aspectRatio?: number;
|
|
16
|
+
className?: string;
|
|
17
|
+
style?: React.CSSProperties;
|
|
18
|
+
containerStyle?: React.CSSProperties;
|
|
19
|
+
svgStyle?: React.CSSProperties;
|
|
20
|
+
loading?: boolean;
|
|
21
|
+
error?: string | Error;
|
|
22
|
+
ariaLabel?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
3
25
|
/**
|
|
4
26
|
* Minimal D3 chart host:
|
|
5
27
|
* - Owns the <svg> element
|
|
@@ -21,10 +43,10 @@ export default function D3Chart({
|
|
|
21
43
|
loading = false,
|
|
22
44
|
error,
|
|
23
45
|
ariaLabel = "Chart"
|
|
24
|
-
}) {
|
|
25
|
-
const containerRef = React.useRef(null);
|
|
26
|
-
const svgRef = React.useRef(null);
|
|
27
|
-
const [containerWidth, setContainerWidth] = React.useState(null);
|
|
46
|
+
}: D3ChartProps): React.ReactElement {
|
|
47
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
48
|
+
const svgRef = React.useRef<SVGSVGElement>(null);
|
|
49
|
+
const [containerWidth, setContainerWidth] = React.useState<number | null>(null);
|
|
28
50
|
|
|
29
51
|
React.useEffect(() => {
|
|
30
52
|
if (!responsive) return;
|
|
@@ -53,7 +75,7 @@ export default function D3Chart({
|
|
|
53
75
|
const svgEl = svgRef.current;
|
|
54
76
|
if (!svgEl) return;
|
|
55
77
|
|
|
56
|
-
const dims = {
|
|
78
|
+
const dims: Dimensions = {
|
|
57
79
|
width: computedWidth ?? 0,
|
|
58
80
|
height: computedHeight ?? 0
|
|
59
81
|
};
|
|
@@ -105,5 +127,3 @@ export default function D3Chart({
|
|
|
105
127
|
</div>
|
|
106
128
|
);
|
|
107
129
|
}
|
|
108
|
-
|
|
109
|
-
|
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
import * as d3 from "d3";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface Dimensions {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Margin {
|
|
9
|
+
top: number;
|
|
10
|
+
right: number;
|
|
11
|
+
bottom: number;
|
|
12
|
+
left: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface LineChartOptions {
|
|
16
|
+
xKey?: string;
|
|
17
|
+
yKey?: string;
|
|
18
|
+
margin?: Margin;
|
|
19
|
+
stroke?: string;
|
|
20
|
+
strokeWidth?: number;
|
|
21
|
+
showAxes?: boolean;
|
|
22
|
+
showGrid?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface GroupedBarChartOptions {
|
|
26
|
+
groups?: string[];
|
|
27
|
+
margin?: Margin;
|
|
28
|
+
xKey?: string;
|
|
29
|
+
colors?: string[];
|
|
30
|
+
barRadius?: number;
|
|
31
|
+
yFormat?: string;
|
|
32
|
+
showGrid?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type DataPoint = Record<string, unknown>;
|
|
36
|
+
|
|
37
|
+
function clear(svg: SVGSVGElement): void {
|
|
4
38
|
d3.select(svg).selectAll("*").remove();
|
|
5
39
|
}
|
|
6
40
|
|
|
7
41
|
export const D3ChartTemplates = {
|
|
8
|
-
lineChart(svg, data, dims, opts = {}) {
|
|
42
|
+
lineChart(svg: SVGSVGElement, data: DataPoint[], dims: Dimensions, opts: LineChartOptions = {}): void {
|
|
9
43
|
const {
|
|
10
44
|
xKey = "x",
|
|
11
45
|
yKey = "y",
|
|
@@ -25,11 +59,11 @@ export const D3ChartTemplates = {
|
|
|
25
59
|
|
|
26
60
|
const g = d3.select(svg).attr("viewBox", `0 0 ${width} ${height}`).append("g").attr("transform", `translate(${margin.left},${margin.top})`);
|
|
27
61
|
|
|
28
|
-
const xs = data.map((d) => d?.[xKey]).filter((v) => v != null);
|
|
29
|
-
const ys = data.map((d) => d?.[yKey]).filter((v) => v != null);
|
|
62
|
+
const xs = data.map((d) => d?.[xKey]).filter((v) => v != null) as number[];
|
|
63
|
+
const ys = data.map((d) => d?.[yKey]).filter((v) => v != null) as number[];
|
|
30
64
|
|
|
31
|
-
const xDomain = d3.extent(xs);
|
|
32
|
-
const yDomain = d3.extent(ys);
|
|
65
|
+
const xDomain = d3.extent(xs) as [number, number];
|
|
66
|
+
const yDomain = d3.extent(ys) as [number, number];
|
|
33
67
|
|
|
34
68
|
const x = d3.scaleLinear().domain(xDomain).nice().range([0, innerW]);
|
|
35
69
|
const y = d3.scaleLinear().domain(yDomain).nice().range([innerH, 0]);
|
|
@@ -37,15 +71,15 @@ export const D3ChartTemplates = {
|
|
|
37
71
|
if (showGrid) {
|
|
38
72
|
g.append("g")
|
|
39
73
|
.attr("class", "grid")
|
|
40
|
-
.call(d3.axisLeft(y).ticks(5).tickSize(-innerW).tickFormat(""))
|
|
41
|
-
.call((grid) => grid.selectAll("line").attr("stroke", "currentColor").attr("opacity", 0.12))
|
|
42
|
-
.call((grid) => grid.selectAll("path").attr("stroke", "none"));
|
|
74
|
+
.call(d3.axisLeft(y).ticks(5).tickSize(-innerW).tickFormat(() => ""))
|
|
75
|
+
.call((grid: d3.Selection<SVGGElement, unknown, null, undefined>) => grid.selectAll("line").attr("stroke", "currentColor").attr("opacity", 0.12))
|
|
76
|
+
.call((grid: d3.Selection<SVGGElement, unknown, null, undefined>) => grid.selectAll("path").attr("stroke", "none"));
|
|
43
77
|
}
|
|
44
78
|
|
|
45
79
|
const line = d3
|
|
46
|
-
.line()
|
|
47
|
-
.x((d) => x(d[xKey]))
|
|
48
|
-
.y((d) => y(d[yKey]))
|
|
80
|
+
.line<DataPoint>()
|
|
81
|
+
.x((d) => x(d[xKey] as number))
|
|
82
|
+
.y((d) => y(d[yKey] as number))
|
|
49
83
|
.defined((d) => d?.[xKey] != null && d?.[yKey] != null);
|
|
50
84
|
|
|
51
85
|
g.append("path")
|
|
@@ -59,15 +93,15 @@ export const D3ChartTemplates = {
|
|
|
59
93
|
g.append("g")
|
|
60
94
|
.attr("transform", `translate(0,${innerH})`)
|
|
61
95
|
.call(d3.axisBottom(x).ticks(6))
|
|
62
|
-
.call((ax) => ax.selectAll("text").attr("font-size", 10));
|
|
96
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll("text").attr("font-size", 10));
|
|
63
97
|
|
|
64
98
|
g.append("g")
|
|
65
99
|
.call(d3.axisLeft(y).ticks(5))
|
|
66
|
-
.call((ax) => ax.selectAll("text").attr("font-size", 10));
|
|
100
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll("text").attr("font-size", 10));
|
|
67
101
|
}
|
|
68
102
|
},
|
|
69
103
|
|
|
70
|
-
groupedBarChart(svg, data, dims, opts = {}) {
|
|
104
|
+
groupedBarChart(svg: SVGSVGElement, data: DataPoint[], dims: Dimensions, opts: GroupedBarChartOptions = {}): void {
|
|
71
105
|
const {
|
|
72
106
|
groups = [],
|
|
73
107
|
margin = { top: 20, right: 20, bottom: 40, left: 55 },
|
|
@@ -90,37 +124,35 @@ export const D3ChartTemplates = {
|
|
|
90
124
|
|
|
91
125
|
const groupKeys = groups.length ? groups : Object.keys(data[0] || {}).filter((k) => k !== xKey);
|
|
92
126
|
|
|
93
|
-
const x0 = d3.scaleBand().domain(data.map((d) => d[xKey])).range([0, innerW]).padding(0.3);
|
|
127
|
+
const x0 = d3.scaleBand().domain(data.map((d: DataPoint) => String(d[xKey]))).range([0, innerW]).padding(0.3);
|
|
94
128
|
const x1 = d3.scaleBand().domain(groupKeys).range([0, x0.bandwidth()]).padding(0.05);
|
|
95
|
-
const yMax = d3.max(data, (d) => d3.max(groupKeys, (k) => d[k])) * 1.15;
|
|
129
|
+
const yMax = (d3.max(data, (d: DataPoint) => d3.max(groupKeys, (k: string) => d[k] as number)) ?? 0) * 1.15;
|
|
96
130
|
const y = d3.scaleLinear().domain([0, yMax]).range([innerH, 0]);
|
|
97
131
|
|
|
98
132
|
g.append("g")
|
|
99
133
|
.attr("transform", `translate(0,${innerH})`)
|
|
100
134
|
.call(d3.axisBottom(x0).tickSize(0))
|
|
101
|
-
.call((ax) => ax.selectAll("text").attr("fill", "#94a3b8").attr("font-size", "11px"))
|
|
102
|
-
.call((ax) => ax.select(".domain").remove());
|
|
135
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll("text").attr("fill", "#94a3b8").attr("font-size", "11px"))
|
|
136
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.select(".domain").remove());
|
|
103
137
|
|
|
104
138
|
g.append("g")
|
|
105
139
|
.call(d3.axisLeft(y).ticks(5).tickFormat(d3.format(yFormat)).tickSize(showGrid ? -innerW : 0))
|
|
106
|
-
.call((ax) => ax.selectAll("text").attr("fill", "#94a3b8").attr("font-size", "10px"))
|
|
107
|
-
.call((ax) => ax.selectAll(".tick line").attr("stroke", "#e2e8f0").attr("stroke-dasharray", "2,2"))
|
|
108
|
-
.call((ax) => ax.select(".domain").remove());
|
|
140
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll("text").attr("fill", "#94a3b8").attr("font-size", "10px"))
|
|
141
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.selectAll(".tick line").attr("stroke", "#e2e8f0").attr("stroke-dasharray", "2,2"))
|
|
142
|
+
.call((ax: d3.Selection<SVGGElement, unknown, null, undefined>) => ax.select(".domain").remove());
|
|
109
143
|
|
|
110
144
|
const rows = g.selectAll(".bar-group").data(data).join("g")
|
|
111
145
|
.attr("class", "bar-group")
|
|
112
|
-
.attr("transform", (d) => `translate(${x0(d[xKey])},0)`);
|
|
146
|
+
.attr("transform", (d: DataPoint) => `translate(${x0(String(d[xKey]))},0)`);
|
|
113
147
|
|
|
114
148
|
groupKeys.forEach((key, i) => {
|
|
115
149
|
rows.append("rect")
|
|
116
|
-
.attr("x", x1(key))
|
|
117
|
-
.attr("y", (d) => y(d[key]))
|
|
150
|
+
.attr("x", x1(key) ?? 0)
|
|
151
|
+
.attr("y", (d: DataPoint) => y(d[key] as number))
|
|
118
152
|
.attr("width", x1.bandwidth())
|
|
119
|
-
.attr("height", (d) => innerH - y(d[key]))
|
|
153
|
+
.attr("height", (d: DataPoint) => innerH - y(d[key] as number))
|
|
120
154
|
.attr("rx", barRadius)
|
|
121
155
|
.attr("fill", colors[i % colors.length]);
|
|
122
156
|
});
|
|
123
157
|
},
|
|
124
158
|
};
|
|
125
|
-
|
|
126
|
-
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import React, { useMemo, useRef, useEffect, useState, useCallback } from "react";
|
|
2
2
|
import * as d3 from "d3";
|
|
3
3
|
import { feature } from "topojson-client";
|
|
4
|
+
import type { Topology, GeometryCollection } from "topojson-specification";
|
|
4
5
|
import world from "world-atlas/land-110m.json";
|
|
5
6
|
|
|
6
|
-
const land = feature(world
|
|
7
|
+
const land = feature(world as unknown as Topology<{ land: GeometryCollection }>, (world.objects.land as unknown) as GeometryCollection);
|
|
8
|
+
|
|
9
|
+
type ProjectionType = "naturalEarth" | "mercator" | "equirectangular";
|
|
7
10
|
|
|
8
11
|
const PROJECTIONS = {
|
|
9
12
|
naturalEarth: d3.geoNaturalEarth1,
|
|
@@ -11,7 +14,30 @@ const PROJECTIONS = {
|
|
|
11
14
|
equirectangular: d3.geoEquirectangular,
|
|
12
15
|
};
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
interface Theme {
|
|
18
|
+
bg: string;
|
|
19
|
+
bgGradient: [string, string];
|
|
20
|
+
land: string;
|
|
21
|
+
landStroke: string;
|
|
22
|
+
sphere: string;
|
|
23
|
+
graticule: string;
|
|
24
|
+
graticuleOpacity: number;
|
|
25
|
+
markerActive: string;
|
|
26
|
+
markerInactive: string;
|
|
27
|
+
label: string;
|
|
28
|
+
labelInactive: string;
|
|
29
|
+
arc: string;
|
|
30
|
+
arcHighlight: string;
|
|
31
|
+
arcDanger: string;
|
|
32
|
+
dot: string;
|
|
33
|
+
dotDanger: string;
|
|
34
|
+
overlayFill: string;
|
|
35
|
+
overlayStroke: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ThemeName = "dark" | "light";
|
|
39
|
+
|
|
40
|
+
const THEMES: Record<ThemeName, Theme> = {
|
|
15
41
|
dark: {
|
|
16
42
|
bg: "#050b15",
|
|
17
43
|
bgGradient: ["#0a1628", "#050b15"],
|
|
@@ -54,11 +80,72 @@ const THEMES = {
|
|
|
54
80
|
},
|
|
55
81
|
};
|
|
56
82
|
|
|
57
|
-
function buildProjection(type, width, height) {
|
|
83
|
+
function buildProjection(type: ProjectionType, width: number, height: number): d3.GeoProjection {
|
|
58
84
|
const factory = PROJECTIONS[type] ?? PROJECTIONS.naturalEarth;
|
|
59
85
|
return factory().fitSize([width, height], { type: "Sphere" });
|
|
60
86
|
}
|
|
61
87
|
|
|
88
|
+
export interface Marker {
|
|
89
|
+
id: string;
|
|
90
|
+
lon: number;
|
|
91
|
+
lat: number;
|
|
92
|
+
label?: string | false;
|
|
93
|
+
active?: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface Arc {
|
|
97
|
+
id: string;
|
|
98
|
+
from: [number, number];
|
|
99
|
+
to: [number, number];
|
|
100
|
+
danger?: boolean;
|
|
101
|
+
color?: string;
|
|
102
|
+
dotColor?: string;
|
|
103
|
+
progress?: number;
|
|
104
|
+
_path?: string | null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface Overlay {
|
|
108
|
+
id: string;
|
|
109
|
+
center: [number, number];
|
|
110
|
+
radius?: number;
|
|
111
|
+
fill?: string;
|
|
112
|
+
stroke?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface InitialBounds {
|
|
116
|
+
sw: [number, number];
|
|
117
|
+
ne: [number, number];
|
|
118
|
+
padding?: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface ChildrenFunctionProps {
|
|
122
|
+
proj: d3.GeoProjection;
|
|
123
|
+
pathGen: d3.GeoPath;
|
|
124
|
+
theme: Theme;
|
|
125
|
+
width: number;
|
|
126
|
+
height: number;
|
|
127
|
+
transform: d3.ZoomTransform;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface GeoMapProps {
|
|
131
|
+
width?: number;
|
|
132
|
+
height?: number;
|
|
133
|
+
projection?: ProjectionType;
|
|
134
|
+
theme?: ThemeName;
|
|
135
|
+
markers?: Marker[];
|
|
136
|
+
arcs?: Arc[];
|
|
137
|
+
overlays?: Overlay[];
|
|
138
|
+
selectedId?: string | null;
|
|
139
|
+
onArcClick?: (arc: Arc) => void;
|
|
140
|
+
onMarkerClick?: (marker: Marker) => void;
|
|
141
|
+
zoomable?: boolean;
|
|
142
|
+
minZoom?: number;
|
|
143
|
+
maxZoom?: number;
|
|
144
|
+
initialBounds?: InitialBounds | null;
|
|
145
|
+
className?: string;
|
|
146
|
+
children?: React.ReactNode | ((props: ChildrenFunctionProps) => React.ReactNode);
|
|
147
|
+
}
|
|
148
|
+
|
|
62
149
|
export default function GeoMap({
|
|
63
150
|
width = 960,
|
|
64
151
|
height = 480,
|
|
@@ -76,11 +163,11 @@ export default function GeoMap({
|
|
|
76
163
|
initialBounds = null,
|
|
77
164
|
className = "",
|
|
78
165
|
children,
|
|
79
|
-
}) {
|
|
166
|
+
}: GeoMapProps): React.ReactElement {
|
|
80
167
|
const t = THEMES[theme] ?? THEMES.dark;
|
|
81
|
-
const svgRef = useRef(null);
|
|
82
|
-
const [transform, setTransform] = useState(d3.zoomIdentity);
|
|
83
|
-
const zoomRef = useRef(null);
|
|
168
|
+
const svgRef = useRef<SVGSVGElement>(null);
|
|
169
|
+
const [transform, setTransform] = useState<d3.ZoomTransform>(d3.zoomIdentity);
|
|
170
|
+
const zoomRef = useRef<d3.ZoomBehavior<SVGSVGElement, unknown> | null>(null);
|
|
84
171
|
|
|
85
172
|
const { proj, pathGen, graticulePath, spherePath, landPath } = useMemo(() => {
|
|
86
173
|
const proj = buildProjection(projType, width, height);
|
|
@@ -98,12 +185,14 @@ export default function GeoMap({
|
|
|
98
185
|
useEffect(() => {
|
|
99
186
|
if (!zoomable || !svgRef.current) return;
|
|
100
187
|
const svg = d3.select(svgRef.current);
|
|
101
|
-
const zoom = d3.zoom()
|
|
188
|
+
const zoom = d3.zoom<SVGSVGElement, unknown>()
|
|
102
189
|
.scaleExtent([minZoom, maxZoom])
|
|
103
|
-
.on("zoom", (e) => setTransform(e.transform));
|
|
190
|
+
.on("zoom", (e: d3.D3ZoomEvent<SVGSVGElement, unknown>) => setTransform(e.transform));
|
|
104
191
|
zoomRef.current = zoom;
|
|
105
192
|
svg.call(zoom);
|
|
106
|
-
return () =>
|
|
193
|
+
return () => {
|
|
194
|
+
svg.on(".zoom", null);
|
|
195
|
+
};
|
|
107
196
|
}, [zoomable, minZoom, maxZoom]);
|
|
108
197
|
|
|
109
198
|
// Apply initial zoom to fit bounds (markers/region) on mount
|
|
@@ -144,15 +233,15 @@ export default function GeoMap({
|
|
|
144
233
|
const isZoomed = transform.k !== 1 || transform.x !== 0 || transform.y !== 0;
|
|
145
234
|
|
|
146
235
|
const arcPaths = useMemo(() => {
|
|
147
|
-
const cache = {};
|
|
236
|
+
const cache: Record<string, string | null> = {};
|
|
148
237
|
arcs.forEach(a => {
|
|
149
238
|
const key = `${a.from[0]},${a.from[1]}-${a.to[0]},${a.to[1]}`;
|
|
150
239
|
if (cache[key]) { a._path = cache[key]; return; }
|
|
151
240
|
const interp = d3.geoInterpolate(a.from, a.to);
|
|
152
|
-
const pts = [];
|
|
241
|
+
const pts: [number, number][] = [];
|
|
153
242
|
for (let i = 0; i <= 1; i += 0.02) {
|
|
154
243
|
const p = proj(interp(i));
|
|
155
|
-
if (p) pts.push(p);
|
|
244
|
+
if (p) pts.push(p as [number, number]);
|
|
156
245
|
}
|
|
157
246
|
const path = pts.length > 1 ? d3.line()(pts) : null;
|
|
158
247
|
cache[key] = path;
|
|
@@ -162,7 +251,7 @@ export default function GeoMap({
|
|
|
162
251
|
}, [arcs, proj]);
|
|
163
252
|
|
|
164
253
|
const activeMarkerIds = useMemo(() => {
|
|
165
|
-
const s = new Set();
|
|
254
|
+
const s = new Set<string>();
|
|
166
255
|
markers.forEach(m => { if (m.active) s.add(m.id); });
|
|
167
256
|
return s;
|
|
168
257
|
}, [markers]);
|
|
@@ -194,9 +283,9 @@ export default function GeoMap({
|
|
|
194
283
|
|
|
195
284
|
{/* Zoomable content group */}
|
|
196
285
|
<g transform={txStr}>
|
|
197
|
-
<path d={spherePath} fill="none" stroke={t.sphere} strokeWidth={0.8 * invScale} />
|
|
198
|
-
<path d={landPath} fill={t.land} stroke={t.landStroke} strokeWidth={0.4 * invScale} />
|
|
199
|
-
<path d={graticulePath} fill="none" stroke={t.graticule} strokeWidth={0.3 * invScale} opacity={t.graticuleOpacity} />
|
|
286
|
+
<path d={spherePath ?? undefined} fill="none" stroke={t.sphere} strokeWidth={0.8 * invScale} />
|
|
287
|
+
<path d={landPath ?? undefined} fill={t.land} stroke={t.landStroke} strokeWidth={0.4 * invScale} />
|
|
288
|
+
<path d={graticulePath ?? undefined} fill="none" stroke={t.graticule} strokeWidth={0.3 * invScale} opacity={t.graticuleOpacity} />
|
|
200
289
|
|
|
201
290
|
{/* Overlay zones (disruptions, weather) */}
|
|
202
291
|
{overlays.map(o => {
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
} from "@heroicons/react/24/outline";
|
|
10
10
|
import ChatMessageList from "./ChatMessageList";
|
|
11
11
|
import ChatInput from "./ChatInput";
|
|
12
|
-
import useChatState from "./useChatState";
|
|
12
|
+
import useChatState, { UseChatStateOptions, ChatMessage } from "./useChatState";
|
|
13
|
+
import { ChatMessageData } from "./ChatMessage";
|
|
13
14
|
|
|
14
15
|
const BACKDROP_VARIANTS = {
|
|
15
16
|
hidden: { opacity: 0 },
|
|
@@ -23,11 +24,22 @@ const PANEL_VARIANTS = {
|
|
|
23
24
|
opacity: 1,
|
|
24
25
|
y: 0,
|
|
25
26
|
scale: 1,
|
|
26
|
-
transition: { type: "spring", damping: 28, stiffness: 380 },
|
|
27
|
+
transition: { type: "spring" as const, damping: 28, stiffness: 380 },
|
|
27
28
|
},
|
|
28
29
|
exit: { opacity: 0, y: 10, scale: 0.97, transition: { duration: 0.12 } },
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
export interface ChatBarProps {
|
|
33
|
+
onSend?: UseChatStateOptions["onSend"];
|
|
34
|
+
suggestions?: string[];
|
|
35
|
+
placeholder?: string;
|
|
36
|
+
title?: string;
|
|
37
|
+
initialMessages?: UseChatStateOptions["initialMessages"];
|
|
38
|
+
className?: string;
|
|
39
|
+
renderAvatar?: (message: ChatMessageData) => React.ReactNode;
|
|
40
|
+
onOpenInTab?: (messages: ChatMessage[]) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
/**
|
|
32
44
|
* Command-palette style AI chat bar.
|
|
33
45
|
*
|
|
@@ -41,17 +53,16 @@ const PANEL_VARIANTS = {
|
|
|
41
53
|
export default function ChatBar({
|
|
42
54
|
onSend,
|
|
43
55
|
suggestions = [],
|
|
44
|
-
placeholder = "Ask a question
|
|
56
|
+
placeholder = "Ask a question…",
|
|
45
57
|
title = "AI Assistant",
|
|
46
58
|
initialMessages = [],
|
|
47
59
|
className = "",
|
|
48
|
-
panelHeight,
|
|
49
60
|
renderAvatar,
|
|
50
61
|
onOpenInTab,
|
|
51
|
-
}) {
|
|
62
|
+
}: ChatBarProps) {
|
|
52
63
|
const [open, setOpen] = useState(false);
|
|
53
64
|
const [portalVisible, setPortalVisible] = useState(false);
|
|
54
|
-
const panelRef = useRef(null);
|
|
65
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
55
66
|
const chat = useChatState({ initialMessages, onSend });
|
|
56
67
|
const isEmpty = chat.messages.length === 0;
|
|
57
68
|
|
|
@@ -62,7 +73,7 @@ export default function ChatBar({
|
|
|
62
73
|
}, [open]);
|
|
63
74
|
|
|
64
75
|
useEffect(() => {
|
|
65
|
-
function onKey(e) {
|
|
76
|
+
function onKey(e: KeyboardEvent) {
|
|
66
77
|
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
67
78
|
e.preventDefault();
|
|
68
79
|
setOpen((prev) => !prev);
|
|
@@ -79,7 +90,7 @@ export default function ChatBar({
|
|
|
79
90
|
return () => { document.body.style.overflow = ""; };
|
|
80
91
|
}, [open]);
|
|
81
92
|
|
|
82
|
-
function handleSend(content) {
|
|
93
|
+
function handleSend(content: string) {
|
|
83
94
|
if (!open) setOpen(true);
|
|
84
95
|
chat.sendMessage(content);
|
|
85
96
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState, useRef, useEffect } from "react";
|
|
2
2
|
import { PaperAirplaneIcon, StopCircleIcon } from "@heroicons/react/24/solid";
|
|
3
3
|
|
|
4
|
+
export interface ChatInputProps {
|
|
5
|
+
onSend?: (content: string) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
isLoading?: boolean;
|
|
8
|
+
onStop?: () => void;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
maxRows?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
/**
|
|
5
14
|
* Chat input with auto-resize textarea, Send button, and keyboard shortcuts.
|
|
6
15
|
* Enter sends, Shift+Enter inserts newline.
|
|
7
|
-
*
|
|
8
|
-
* @param {Function} onSend — (content: string) => void
|
|
9
|
-
* @param {boolean} disabled — disable input while agent is processing
|
|
10
|
-
* @param {boolean} isLoading — show stop button instead of send
|
|
11
|
-
* @param {Function} onStop — optional: called when stop is clicked
|
|
12
|
-
* @param {string} placeholder
|
|
13
|
-
* @param {number} maxRows — max visible rows before scroll (default 6)
|
|
14
16
|
*/
|
|
15
17
|
export default function ChatInput({
|
|
16
18
|
onSend,
|
|
@@ -19,9 +21,9 @@ export default function ChatInput({
|
|
|
19
21
|
onStop,
|
|
20
22
|
placeholder = "Type a message…",
|
|
21
23
|
maxRows = 6,
|
|
22
|
-
}) {
|
|
24
|
+
}: ChatInputProps) {
|
|
23
25
|
const [value, setValue] = useState("");
|
|
24
|
-
const textareaRef = useRef(null);
|
|
26
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
25
27
|
|
|
26
28
|
useEffect(() => {
|
|
27
29
|
const ta = textareaRef.current;
|
|
@@ -41,7 +43,7 @@ export default function ChatInput({
|
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
function handleKeyDown(e) {
|
|
46
|
+
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
|
45
47
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
46
48
|
e.preventDefault();
|
|
47
49
|
handleSend();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { renderSchemaComponent } from "@/components/workspace/ComponentRegistry";
|
|
3
|
-
import ChatToolCall from "./ChatToolCall";
|
|
3
|
+
import ChatToolCall, { ToolCall } from "./ChatToolCall";
|
|
4
4
|
import { UserCircleIcon, CpuChipIcon } from "@heroicons/react/24/solid";
|
|
5
5
|
|
|
6
|
-
function cx(...classes) {
|
|
6
|
+
function cx(...classes: (string | boolean | undefined)[]): string {
|
|
7
7
|
return classes.filter(Boolean).join(" ");
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -11,9 +11,9 @@ function cx(...classes) {
|
|
|
11
11
|
* Lightweight inline formatter for assistant messages.
|
|
12
12
|
* Handles code blocks, inline code, bold, italic, and line breaks.
|
|
13
13
|
*/
|
|
14
|
-
function formatContent(text) {
|
|
14
|
+
function formatContent(text: string): React.ReactNode {
|
|
15
15
|
if (!text) return null;
|
|
16
|
-
const parts = [];
|
|
16
|
+
const parts: React.ReactNode[] = [];
|
|
17
17
|
let key = 0;
|
|
18
18
|
|
|
19
19
|
const codeBlockRegex = /```(\w*)\n?([\s\S]*?)```/g;
|
|
@@ -46,7 +46,7 @@ function formatContent(text) {
|
|
|
46
46
|
return parts;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function formatInline(text) {
|
|
49
|
+
function formatInline(text: string): React.ReactNode {
|
|
50
50
|
const tokens = text.split(/(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*)/g);
|
|
51
51
|
return tokens.map((token, i) => {
|
|
52
52
|
if (token.startsWith("**") && token.endsWith("**")) {
|
|
@@ -74,13 +74,26 @@ function formatInline(text) {
|
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
export interface ChatMessageData {
|
|
78
|
+
id: string;
|
|
79
|
+
role: "user" | "assistant" | "system";
|
|
80
|
+
content?: string;
|
|
81
|
+
components?: unknown[];
|
|
82
|
+
toolCalls?: ToolCall[];
|
|
83
|
+
isError?: boolean;
|
|
84
|
+
isStreaming?: boolean;
|
|
85
|
+
timestamp?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ChatMessageProps {
|
|
89
|
+
message: ChatMessageData;
|
|
90
|
+
avatar?: React.ReactNode;
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
/**
|
|
78
94
|
* Renders a single chat message.
|
|
79
|
-
*
|
|
80
|
-
* @param {Object} message — { id, role, content, components?, toolCalls?, isError?, isStreaming?, timestamp? }
|
|
81
|
-
* @param {React.ReactNode} avatar — custom avatar override
|
|
82
95
|
*/
|
|
83
|
-
export default function ChatMessage({ message, avatar }) {
|
|
96
|
+
export default function ChatMessage({ message, avatar }: ChatMessageProps) {
|
|
84
97
|
const isUser = message.role === "user";
|
|
85
98
|
const isSystem = message.role === "system";
|
|
86
99
|
const isAssistant = message.role === "assistant";
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import React, { useRef, useEffect } from "react";
|
|
2
|
-
import ChatMessage from "./ChatMessage";
|
|
2
|
+
import ChatMessage, { ChatMessageData } from "./ChatMessage";
|
|
3
3
|
import ChatTypingIndicator from "./ChatTypingIndicator";
|
|
4
4
|
import ChatSuggestions from "./ChatSuggestions";
|
|
5
5
|
|
|
6
|
+
export interface ChatMessageListProps {
|
|
7
|
+
messages?: ChatMessageData[];
|
|
8
|
+
isLoading?: boolean;
|
|
9
|
+
isStreaming?: boolean;
|
|
10
|
+
suggestions?: string[];
|
|
11
|
+
onSuggestion?: (text: string) => void;
|
|
12
|
+
renderAvatar?: (message: ChatMessageData) => React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
/**
|
|
7
16
|
* Scrollable message area with auto-scroll to latest message.
|
|
8
|
-
*
|
|
9
|
-
* @param {Array} messages — array of message objects
|
|
10
|
-
* @param {boolean} isLoading — show typing indicator
|
|
11
|
-
* @param {boolean} isStreaming — agent is streaming (show different indicator text)
|
|
12
|
-
* @param {string[]} suggestions — follow-up suggestions shown after last assistant message
|
|
13
|
-
* @param {Function} onSuggestion — (text) => void
|
|
14
|
-
* @param {Function} renderAvatar — (message) => ReactNode, optional per-message avatar
|
|
15
17
|
*/
|
|
16
18
|
export default function ChatMessageList({
|
|
17
19
|
messages = [],
|
|
@@ -20,9 +22,9 @@ export default function ChatMessageList({
|
|
|
20
22
|
suggestions = [],
|
|
21
23
|
onSuggestion,
|
|
22
24
|
renderAvatar,
|
|
23
|
-
}) {
|
|
24
|
-
const bottomRef = useRef(null);
|
|
25
|
-
const containerRef = useRef(null);
|
|
25
|
+
}: ChatMessageListProps) {
|
|
26
|
+
const bottomRef = useRef<HTMLDivElement>(null);
|
|
27
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
26
28
|
|
|
27
29
|
useEffect(() => {
|
|
28
30
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
@@ -2,23 +2,26 @@ import React from "react";
|
|
|
2
2
|
import ChatMessageList from "./ChatMessageList";
|
|
3
3
|
import ChatInput from "./ChatInput";
|
|
4
4
|
import ChatWelcome from "./ChatWelcome";
|
|
5
|
-
import useChatState from "./useChatState";
|
|
5
|
+
import useChatState, { UseChatStateOptions } from "./useChatState";
|
|
6
|
+
import { ChatMessageData } from "./ChatMessage";
|
|
6
7
|
import { TrashIcon } from "@heroicons/react/24/outline";
|
|
7
8
|
|
|
9
|
+
export interface ChatPanelProps {
|
|
10
|
+
title?: string;
|
|
11
|
+
onSend?: UseChatStateOptions["onSend"];
|
|
12
|
+
initialMessages?: UseChatStateOptions["initialMessages"];
|
|
13
|
+
welcomeTitle?: string;
|
|
14
|
+
welcomeSubtitle?: string;
|
|
15
|
+
suggestions?: string[];
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
className?: string;
|
|
18
|
+
renderAvatar?: (message: ChatMessageData) => React.ReactNode;
|
|
19
|
+
showHeader?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
/**
|
|
9
23
|
* All-in-one chat panel. Composes ChatMessageList, ChatInput, ChatWelcome,
|
|
10
24
|
* and useChatState into a single drop-in component.
|
|
11
|
-
*
|
|
12
|
-
* @param {string} title — panel header title
|
|
13
|
-
* @param {Function} onSend — async (userMessage, history, helpers) => assistantMessage?
|
|
14
|
-
* @param {Array} initialMessages — seed messages
|
|
15
|
-
* @param {string} welcomeTitle — welcome screen heading
|
|
16
|
-
* @param {string} welcomeSubtitle — welcome screen description
|
|
17
|
-
* @param {string[]} suggestions — starter and follow-up prompts
|
|
18
|
-
* @param {string} placeholder — input placeholder
|
|
19
|
-
* @param {string} className — additional classes on the root container
|
|
20
|
-
* @param {Function} renderAvatar — (message) => ReactNode
|
|
21
|
-
* @param {boolean} showHeader — show the title bar (default true)
|
|
22
25
|
*/
|
|
23
26
|
export default function ChatPanel({
|
|
24
27
|
title = "AI Assistant",
|
|
@@ -31,7 +34,7 @@ export default function ChatPanel({
|
|
|
31
34
|
className = "",
|
|
32
35
|
renderAvatar,
|
|
33
36
|
showHeader = true,
|
|
34
|
-
}) {
|
|
37
|
+
}: ChatPanelProps) {
|
|
35
38
|
const chat = useChatState({ initialMessages, onSend });
|
|
36
39
|
|
|
37
40
|
const isEmpty = chat.messages.length === 0;
|