@nethru/kit 1.1.9 → 1.1.11

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.
@@ -220,7 +220,8 @@ function toOptions(props, legendLimitEnabled, handleLegendItemClick) {
220
220
  autoRotation: false,
221
221
  format: xAxisLabelFormat ? `{value:${xAxisLabelFormat}}` : undefined,
222
222
  style: {
223
- color: grey[600]
223
+ color: grey[600],
224
+ fontSize: '12px'
224
225
  }
225
226
  },
226
227
  categories: categorize ? categories : undefined,
@@ -278,7 +279,12 @@ function toOptions(props, legendLimitEnabled, handleLegendItemClick) {
278
279
  legend: {
279
280
  align: legendAlign,
280
281
  verticalAlign: 'top',
281
- enabled: showLegend
282
+ enabled: showLegend,
283
+ itemStyle: {
284
+ fontFamily: fontFamily,
285
+ fontSize: '13px',
286
+ fontWeight: '300'
287
+ }
282
288
  },
283
289
  tooltip: {
284
290
  followPointer: false,
@@ -346,6 +352,7 @@ const tooltipFormatter = (_this, props) => {
346
352
  function formatDate(data, format) {
347
353
  return Highcharts.dateFormat(format, data.getTime() - data.getTimezoneOffset() * 60000);
348
354
  }
355
+ export const fontFamily = 'Pretendard, -apple-system, BlinkMacSystemFont, sans-serif';
349
356
  const ZIndex = {
350
357
  AVERAGE_LINE_BASE: 10,
351
358
  COMPARE_CHART_BASE: 5,
@@ -2,6 +2,7 @@ import React, { useMemo } from 'react';
2
2
  import Highcharts from 'highcharts';
3
3
  import HighchartsReactModule from 'highcharts-react-official';
4
4
  import 'highcharts/modules/pattern-fill.js';
5
+ import { fontFamily } from "./Chart";
5
6
 
6
7
  // Handle both ES module and CommonJS exports
7
8
  import { jsx as _jsx } from "react/jsx-runtime";
@@ -57,6 +58,7 @@ const ColumnChart = ({
57
58
  labels: {
58
59
  style: {
59
60
  color: '#777',
61
+ fontSize: '12px',
60
62
  fontFamily: fontFamily,
61
63
  fontWeight: '400'
62
64
  }
@@ -104,7 +106,6 @@ const ColumnChart = ({
104
106
  });
105
107
  };
106
108
  export default ColumnChart;
107
- const fontFamily = 'Pretendard, -apple-system, BlinkMacSystemFont, sans-serif';
108
109
  const styles = {
109
110
  chartWrapper: {
110
111
  background: 'transparent',
@@ -1,3 +1,4 @@
1
+ import { formatDuration } from "./Time";
1
2
  export function toValue(value) {
2
3
  return value && typeof value === 'object' ? value.value : value;
3
4
  }
@@ -16,19 +17,6 @@ export function formatPercent(value) {
16
17
  export function formatDiffPercent(value) {
17
18
  return value !== undefined ? `${value > 0 ? '+' : ''}${formatToPercent(value)}` : '-';
18
19
  }
19
- export function formatDuration(value) {
20
- value = toValue(value);
21
- if (!value) return '-';
22
- let day = Math.floor(value / (60 * 60 * 24));
23
- let hour = Math.floor(value / (60 * 60)) - day * 24;
24
- let minute = Math.floor(value / 60) - (day * 24 + hour) * 60;
25
- let second = Math.round(value % 60);
26
- day = day > 0 ? `${day.toLocaleString()}일 ` : '';
27
- hour = hour > 0 ? `${hour >= 10 ? hour : '0' + hour}시간 ` : '';
28
- minute = `${minute >= 10 ? minute : '0' + minute}분 `;
29
- second = `${second >= 10 ? second : '0' + second}초`;
30
- return `${day}${hour}${minute}${second}`;
31
- }
32
20
  export function formatBytes(value) {
33
21
  return value !== undefined ? `${value.toLocaleString()} bytes` : '-';
34
22
  }
@@ -0,0 +1,49 @@
1
+ import React, { useMemo } from "react";
2
+ import { blue } from "@nethru/ui/base/colors";
3
+ import Chart from "./Chart";
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ export default function PeriodCompareChart({
6
+ metrics,
7
+ records,
8
+ averages,
9
+ isDaily = false,
10
+ xPlotIndex,
11
+ xAxisTickInterval,
12
+ yAxisLabelEnabled = false,
13
+ legendLimit = 3,
14
+ colors = defaultColors,
15
+ ...props
16
+ }) {
17
+ const yAxisPlotLines = useMemo(() => {
18
+ return averages.map((average, index) => {
19
+ return {
20
+ ...average,
21
+ color: index > 0 ? colors[index][0] : colors[index],
22
+ labelColor: 'black',
23
+ labelAlign: index === 1 ? 'right' : 'left'
24
+ };
25
+ });
26
+ }, [averages]);
27
+ const tooltip = useMemo(() => ({
28
+ headerVisible: !isDaily,
29
+ dateFormat: isDaily ? '' : '%H:%M',
30
+ legendColors: [colors[0], blue[300]]
31
+ }), [isDaily]);
32
+ return /*#__PURE__*/_jsx(Chart, {
33
+ type: "area",
34
+ metrics: metrics,
35
+ records: records,
36
+ categorize: true,
37
+ xAxisType: "datetime",
38
+ xAxisLabelFormat: isDaily ? '%m-%d' : '%H:%M',
39
+ xPlotIndex: xPlotIndex,
40
+ xAxisTickInterval: xAxisTickInterval,
41
+ yAxisPlotLines: yAxisPlotLines,
42
+ yAxisLabelEnabled: yAxisLabelEnabled,
43
+ legendLimit: legendLimit,
44
+ colors: colors,
45
+ tooltip: tooltip,
46
+ ...props
47
+ });
48
+ }
49
+ const defaultColors = [blue[500], [blue[200], blue[100]]];
@@ -2,6 +2,7 @@ import React, { useMemo } from 'react';
2
2
  import Highcharts from 'highcharts';
3
3
  import HighchartsReactModule from 'highcharts-react-official';
4
4
  import 'highcharts/modules/pattern-fill.js';
5
+ import { fontFamily } from "./Chart";
5
6
 
6
7
  // Handle both ES module and CommonJS exports
7
8
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -27,7 +28,13 @@ const PieChart = ({
27
28
  width: width,
28
29
  height: height,
29
30
  style: {
30
- fontFamily: fontFamily
31
+ fontFamily: fontFamily,
32
+ overflow: 'visible'
33
+ },
34
+ events: {
35
+ load() {
36
+ this.container.parentElement.style.overflow = 'visible';
37
+ }
31
38
  }
32
39
  },
33
40
  title: {
@@ -107,7 +114,6 @@ const PieChart = ({
107
114
  });
108
115
  };
109
116
  export default PieChart;
110
- const fontFamily = 'Pretendard, -apple-system, BlinkMacSystemFont, sans-serif';
111
117
  const styles = {
112
118
  chartWrapper: {
113
119
  background: 'transparent',
@@ -0,0 +1,24 @@
1
+ import { toValue } from "./Number";
2
+ export function formatMinutes(minutes) {
3
+ if (!Number.isInteger(minutes) || minutes < 0) {
4
+ throw new Error("minutes는 0 이상의 정수여야 합니다");
5
+ }
6
+ const hours = Math.floor(minutes / 60);
7
+ const mins = minutes % 60;
8
+ if (hours === 0) return `${mins}분`;
9
+ if (mins === 0) return `${hours}시간`;
10
+ return `${hours}시간 ${mins}분`;
11
+ }
12
+ export function formatDuration(value) {
13
+ value = toValue(value);
14
+ if (!value) return '-';
15
+ let day = Math.floor(value / (60 * 60 * 24));
16
+ let hour = Math.floor(value / (60 * 60)) - day * 24;
17
+ let minute = Math.floor(value / 60) - (day * 24 + hour) * 60;
18
+ let second = Math.round(value % 60);
19
+ day = day > 0 ? `${day.toLocaleString()}일 ` : '';
20
+ hour = hour > 0 ? `${hour >= 10 ? hour : '0' + hour}시간 ` : '';
21
+ minute = `${minute >= 10 ? minute : '0' + minute}분 `;
22
+ second = `${second >= 10 ? second : '0' + second}초`;
23
+ return `${day}${hour}${minute}${second}`;
24
+ }
@@ -1,10 +1,13 @@
1
1
  import React, { useMemo } from "react";
2
- import { Box } from '@mui/material';
2
+ import { Box, Stack } from '@mui/material';
3
3
  import { useToolContext } from "../contexts/ToolContext";
4
4
  import ColumnChartContent from "./wisecollector/ColumnChartContent";
5
5
  import StackedAreaChartContent from "./wisecollector/StackedAreaChartContent";
6
6
  import PieChartContent from "./wisecollector/PieChartContent";
7
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
7
+ import PeriodCompareChartContent from "./wisecollector/PeriodCompareChartContent";
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ const summaryToolNames = ['query-summary-by-container-id', 'query-summary-by-task-id'];
10
+ const trendToolNames = ['query-trend-by-container-id', 'query-trend-by-task-id'];
8
11
  export default function ToolContent() {
9
12
  const {
10
13
  kinds,
@@ -12,14 +15,23 @@ export default function ToolContent() {
12
15
  } = useToolContext();
13
16
  const hasCompare = kinds.includes('COMPARE');
14
17
  const jsonTools = [];
15
- const querySummaryTools = useMemo(() => tools.filter(t => t.name === 'query-summary-by-task-id'), [tools]);
16
- const queryTrendTools = useMemo(() => groupBySamePeriod(tools), [tools]);
17
- return /*#__PURE__*/_jsxs(_Fragment, {
18
- children: [hasCompare && querySummaryTools.length >= 2 ? /*#__PURE__*/_jsx(ColumnChartContent, {
19
- tools: querySummaryTools
20
- }) : querySummaryTools.map((tool, index) => /*#__PURE__*/_jsx(PieChartContent, {
18
+ const summaryTools = useMemo(() => tools.filter(t => summaryToolNames.includes(t.name)), [tools]);
19
+ const {
20
+ periodTrendTools,
21
+ trendTools
22
+ } = useMemo(() => groupTools(hasCompare, tools), [tools]);
23
+ const toolCount = useMemo(() => summaryTools.length + periodTrendTools.length + trendTools.length + jsonTools.length, [summaryTools, periodTrendTools, trendTools, jsonTools]);
24
+ console.log(summaryTools, periodTrendTools, trendTools, jsonTools);
25
+ return /*#__PURE__*/_jsxs(Stack, {
26
+ gap: 5,
27
+ marginBottom: toolCount > 0 ? 5 : 0,
28
+ children: [hasCompare && summaryTools.length >= 2 ? /*#__PURE__*/_jsx(ColumnChartContent, {
29
+ tools: summaryTools
30
+ }) : summaryTools.map((tool, index) => /*#__PURE__*/_jsx(PieChartContent, {
21
31
  tool: tool
22
- }, index)), queryTrendTools.map((tools, index1) => {
32
+ }, index)), periodTrendTools.map((tools, index) => /*#__PURE__*/_jsx(PeriodCompareChartContent, {
33
+ tools: tools
34
+ }, index)), trendTools.map((tools, index1) => {
23
35
  if (hasCompare && tools.length >= 2) return /*#__PURE__*/_jsx(StackedAreaChartContent, {
24
36
  tools: tools
25
37
  }, index1);else {
@@ -43,21 +55,40 @@ export default function ToolContent() {
43
55
  }, index))]
44
56
  });
45
57
  }
46
- function groupBySamePeriod(tools) {
58
+ function groupTools(hasCompare, tools) {
59
+ const periodKey = t => `${t.arguments.from}~${t.arguments.to}`;
60
+ const idMap = new Map();
47
61
  const periodMap = new Map();
48
- tools.filter(t => t.name === 'query-trend-by-task-id').forEach(tool => {
49
- const period = key(tool);
50
- if (!periodMap.has(period)) periodMap.set(period, []);
51
- periodMap.get(period).push(tool);
52
- });
53
- return Array.from(periodMap.values());
54
- function key(tool) {
55
- const {
56
- from,
57
- to
58
- } = tool.arguments;
59
- return `${from}-${to}`;
62
+ const used = new Set();
63
+ const filtered = tools.filter(t => trendToolNames.includes(t.name));
64
+ const periodTrendTools = [];
65
+ if (hasCompare) {
66
+ for (const tool of filtered) {
67
+ const id = tool.arguments.containerId ?? tool.arguments.taskId;
68
+ if (!idMap.has(id)) idMap.set(id, []);
69
+ idMap.get(id).push(tool);
70
+ }
71
+ for (const [id, items] of idMap.entries()) {
72
+ if (!id) continue;
73
+ if (items.length < 2) continue;
74
+ const distinctPeriods = new Set(items.map(periodKey));
75
+ if (distinctPeriods.size < 2) continue;
76
+ periodTrendTools.push(items.sort((a, b) => b.arguments.from - a.arguments.from));
77
+ for (const item of items) {
78
+ used.add(item);
79
+ }
80
+ }
81
+ }
82
+ for (const tool of filtered) {
83
+ if (used.has(tool)) continue;
84
+ const key = periodKey(tool);
85
+ if (!periodMap.has(key)) periodMap.set(key, []);
86
+ periodMap.get(key).push(tool);
60
87
  }
88
+ return {
89
+ periodTrendTools,
90
+ trendTools: Array.from(periodMap.values())
91
+ };
61
92
  }
62
93
  const styles = {
63
94
  contentTool: {
@@ -7,7 +7,7 @@ const ColumnChartContent = ({
7
7
  tools = []
8
8
  }) => {
9
9
  const {
10
- configMap
10
+ getName
11
11
  } = useToolContext();
12
12
  return /*#__PURE__*/_jsx(_Fragment, {
13
13
  children: tools.length > 0 && /*#__PURE__*/_jsx("div", {
@@ -17,25 +17,29 @@ const ColumnChartContent = ({
17
17
  marginBottom: '30px'
18
18
  },
19
19
  children: /*#__PURE__*/_jsx(ColumnChart, {
20
- data: toData(tools, configMap)
20
+ data: toData(tools, getName)
21
21
  })
22
22
  })
23
23
  });
24
24
  };
25
- function toData(tools, configMap) {
25
+ function toData(tools, getName) {
26
26
  const items = [];
27
- const hasConvert = tools.some(content => content.result?.result[0]?.metric?.filter !== undefined);
28
- tools.sort((a, b) => a.arguments.from.localeCompare(b.arguments.from)).forEach(content => {
27
+ const hasConvert = tools.some(tool => {
28
+ const result = tool?.result?.result;
29
+ return Array.isArray(result) ? result[0]?.metric?.filter !== undefined : result?.filter !== undefined;
30
+ });
31
+ tools.sort((a, b) => a.arguments.from.localeCompare(b.arguments.from)).forEach(tool => {
29
32
  const {
33
+ containerId,
30
34
  taskId,
31
35
  from,
32
36
  to
33
- } = content.arguments;
37
+ } = tool.arguments;
34
38
  const item = {
35
- name: `<b>${configMap.get(taskId)}</b> <span style="font-size:10px">(${formatDateRange(from, to)})</span>`,
39
+ name: `<b>${getName(containerId, taskId)}</b> <span style="font-size:10px">(${formatDateRange(from, to)})</span>`,
36
40
  data: []
37
41
  };
38
- const result = reduce(content.result.result);
42
+ const result = Array.isArray(tool.result.result) ? reduce(tool.result.result) : tool.result.result;
39
43
  item.data.push(result.input);
40
44
  item.data.push(result.output);
41
45
  if (hasConvert) item.data.push(result.filter);
@@ -0,0 +1,119 @@
1
+ import React, { useMemo } from 'react';
2
+ import dayjs from 'dayjs';
3
+ import { formatDateRange, isOneDay } from "../../../../js/dateHelper";
4
+ import PeriodCompareChart from "../../../charts/PeriodCompareChart";
5
+ import { useToolContext } from "../../contexts/ToolContext";
6
+ import { formatDuration } from "../../../charts/Number";
7
+ import { formatMinutes } from "../../../charts/Time";
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ export default function PeriodCompareChartContent({
10
+ tools
11
+ }) {
12
+ const chartData = useMemo(() => toChartData(tools), [tools]);
13
+ return /*#__PURE__*/_jsxs("div", {
14
+ children: [/*#__PURE__*/_jsx(Title, {
15
+ tools: tools
16
+ }), /*#__PURE__*/_jsx(PeriodCompareChart, {
17
+ ...chartData,
18
+ height: 220
19
+ })]
20
+ });
21
+ }
22
+ function Title({
23
+ tools
24
+ }) {
25
+ const {
26
+ getName
27
+ } = useToolContext();
28
+ const {
29
+ containerId,
30
+ taskId
31
+ } = tools[0].arguments;
32
+ return /*#__PURE__*/_jsx("div", {
33
+ style: styles.title,
34
+ children: getName(containerId, taskId)
35
+ });
36
+ }
37
+ function toChartData(tools) {
38
+ const current = toSeries(tools[0]);
39
+ const prev = toSeries(tools[1]);
40
+ const currentSum = sum(current.records, 'output');
41
+ const records = prev.records.map((record, index) => ({
42
+ time: current.records[index]?.time ?? record.time,
43
+ diffTime: record.time,
44
+ metric: {
45
+ current: index < current.records.length ? current.records[index].metric.output : undefined,
46
+ prev: record.metric.output
47
+ }
48
+ }));
49
+ const averages = [{
50
+ label: `${formatMinutes(current.unit)} 평균 (${current.period})`,
51
+ value: current.records.length ? currentSum / current.records.length : null
52
+ }, {
53
+ label: `${formatMinutes(current.unit)} 평균 (${prev.period})`,
54
+ value: prev.records.length ? sum(prev.records, 'output') / prev.records.length : null
55
+ }];
56
+ return {
57
+ dimension: 'time',
58
+ metrics: [{
59
+ metric: 'current',
60
+ chartType: 'line'
61
+ }, {
62
+ metric: 'prev',
63
+ chartType: 'gradient'
64
+ }],
65
+ metas: {
66
+ time: {
67
+ name: '일시',
68
+ type: 'DATE'
69
+ },
70
+ current: {
71
+ name: current.period,
72
+ type: 'NUMBER',
73
+ pointerNames: current.isDaily && getPointerNames(current.records)
74
+ },
75
+ prev: {
76
+ name: prev.period,
77
+ type: 'NUMBER',
78
+ pointerNames: current.isDaily && getPointerNames(prev.records)
79
+ }
80
+ },
81
+ records: records,
82
+ averages: averages,
83
+ isDaily: current.isDaily,
84
+ xPlotIndex: current.records.length - 1,
85
+ secondaryXAxis: records.map(r => r.diffTime),
86
+ opacity: 1
87
+ };
88
+ }
89
+ function toSeries(tool) {
90
+ if (!tool) return {
91
+ records: [],
92
+ period: '',
93
+ unit: 30
94
+ };
95
+ const {
96
+ from,
97
+ to,
98
+ unit
99
+ } = tool.arguments;
100
+ return {
101
+ records: tool.result.result,
102
+ period: formatDateRange(from, to),
103
+ unit: Number(unit),
104
+ isDaily: !isOneDay(from, to)
105
+ };
106
+ }
107
+ function sum(records, field) {
108
+ return records.map(record => record.metric[field]).reduce((accumulator, current) => accumulator + current, 0);
109
+ }
110
+ function getPointerNames(records) {
111
+ return records.map(record => dayjs(record.time).format('YYYY-MM-DD'));
112
+ }
113
+ const styles = {
114
+ title: {
115
+ fontSize: '14px',
116
+ fontWeight: '500',
117
+ textAlign: 'center'
118
+ }
119
+ };
@@ -13,11 +13,7 @@ const PieChartContent = ({
13
13
  children: [/*#__PURE__*/_jsx(Title, {
14
14
  tool: tool
15
15
  }), /*#__PURE__*/_jsx("div", {
16
- style: {
17
- display: 'flex',
18
- justifyContent: 'center',
19
- marginBottom: '30px'
20
- },
16
+ style: styles.container,
21
17
  children: toData(tool).map((item, index) => /*#__PURE__*/_jsx(PieChart, {
22
18
  data: item,
23
19
  width: width
@@ -29,33 +25,41 @@ const Title = ({
29
25
  tool
30
26
  }) => {
31
27
  const {
32
- configMap
28
+ getName
33
29
  } = useToolContext();
34
30
  const {
31
+ containerId,
35
32
  taskId,
36
33
  from,
37
34
  to
38
35
  } = tool.arguments;
39
36
  return /*#__PURE__*/_jsxs("div", {
40
- style: titleStyles,
37
+ style: styles.title,
41
38
  children: [/*#__PURE__*/_jsx("div", {
42
- children: configMap.get(taskId)
39
+ children: getName(containerId, taskId)
43
40
  }), /*#__PURE__*/_jsxs("div", {
44
- style: subtitleStyles,
41
+ style: styles.subtitle,
45
42
  children: ["(", formatDateRange(from, to), ")"]
46
43
  })]
47
44
  });
48
45
  };
49
46
  function toData(tool) {
50
47
  const items = [];
51
- tool?.result?.result?.forEach(agent => {
48
+ const result = tool?.result?.result;
49
+ if (Array.isArray(result)) {
50
+ result.forEach(row => {
51
+ add(row.metric);
52
+ });
53
+ } else add(result);
54
+ return items;
55
+ function add(row, name) {
52
56
  const {
53
57
  output,
54
58
  filter,
55
59
  error
56
- } = agent.metric;
60
+ } = row;
57
61
  const item = {
58
- name: agent.name,
62
+ name: name ?? row.name,
59
63
  metrics: []
60
64
  };
61
65
  item.metrics.push({
@@ -74,16 +78,22 @@ function toData(tool) {
74
78
  value: error
75
79
  });
76
80
  items.push(item);
77
- });
78
- return items;
81
+ }
79
82
  }
80
- const titleStyles = {
81
- fontSize: '14px',
82
- fontWeight: '500',
83
- textAlign: 'center'
84
- };
85
- const subtitleStyles = {
86
- fontSize: '10px',
87
- fontWeight: '100'
83
+ const styles = {
84
+ container: {
85
+ display: 'flex',
86
+ justifyContent: 'center',
87
+ marginBottom: '30px'
88
+ },
89
+ title: {
90
+ fontSize: '14px',
91
+ fontWeight: '500',
92
+ textAlign: 'center'
93
+ },
94
+ subtitle: {
95
+ fontSize: '10px',
96
+ fontWeight: '100'
97
+ }
88
98
  };
89
99
  export default PieChartContent;
@@ -7,11 +7,11 @@ export default function StackedAreaChartContent({
7
7
  tools
8
8
  }) {
9
9
  const {
10
- configMap
10
+ getName
11
11
  } = useToolContext();
12
12
  return /*#__PURE__*/_jsx("div", {
13
13
  children: /*#__PURE__*/_jsx(StackedAreaTrendChart, {
14
- metrics: toMetrics(tools, configMap),
14
+ metrics: toMetrics(tools, getName),
15
15
  records: fillRecords(tools, toRecords(tools)),
16
16
  isDaily: isDaily(tools),
17
17
  xPlotIndex: xPlotIndex(tools),
@@ -20,10 +20,10 @@ export default function StackedAreaChartContent({
20
20
  })
21
21
  });
22
22
  }
23
- function toMetrics(tools, configMap) {
23
+ function toMetrics(tools, getName) {
24
24
  return tools.map(tool => ({
25
25
  id: tool.arguments.taskId,
26
- name: configMap.get(tool.arguments.taskId),
26
+ name: getName(tool.arguments.containerId, tool.arguments.taskId),
27
27
  type: 'NUMBER'
28
28
  }));
29
29
  }
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useMemo } from "react";
1
+ import React, { createContext, useCallback, useContext, useMemo } from "react";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  const ToolContext = /*#__PURE__*/createContext();
4
4
  export function ToolContextProvider({
@@ -7,12 +7,18 @@ export function ToolContextProvider({
7
7
  }) {
8
8
  const kinds = useMemo(() => message.kinds ?? [], [message]);
9
9
  const tools = useMemo(() => message.content.filter(c => c.type === 'tool'), [message]);
10
- const configMap = useMemo(() => makeTaskConfigs(tools), [tools]);
10
+ const containerConfigMap = useMemo(() => makeContainerConfigs(tools), [tools]);
11
+ const taskConfigMap = useMemo(() => makeTaskConfigs(tools), [tools]);
12
+ const getName = useCallback((containerId, taskId) => {
13
+ return containerConfigMap.get(containerId) || taskConfigMap.get(taskId);
14
+ }, [containerConfigMap, taskConfigMap]);
11
15
  return /*#__PURE__*/_jsx(ToolContext.Provider, {
12
16
  value: {
13
17
  kinds: kinds,
14
18
  tools,
15
- configMap
19
+ getName,
20
+ containerConfigMap,
21
+ taskConfigMap
16
22
  },
17
23
  children: children
18
24
  });
@@ -20,6 +26,15 @@ export function ToolContextProvider({
20
26
  export function useToolContext() {
21
27
  return useContext(ToolContext);
22
28
  }
29
+ function makeContainerConfigs(tools) {
30
+ const map = new Map();
31
+ tools.filter(t => t.name === 'search-container-by-keyword').forEach(tool => {
32
+ tool.result?.result?.forEach(item => {
33
+ map.set(item.id, item.name);
34
+ });
35
+ });
36
+ return map;
37
+ }
23
38
  function makeTaskConfigs(tools) {
24
39
  const map = new Map();
25
40
  tools.filter(t => t.name === 'search-task-by-keyword').forEach(tool => {
@@ -1,3 +1,22 @@
1
+ const mockSearchContainerByKeyword = {
2
+ "type": "tool",
3
+ "name": "search-container-by-keyword",
4
+ "arguments": {
5
+ "keyword": "와이즈컬렉터3"
6
+ },
7
+ "result": {
8
+ "code": "SUCCESS",
9
+ "message": "Found 1 container(s) matching keyword: '와이즈컬렉터3'",
10
+ "result": [{
11
+ "id": "00001",
12
+ "type": "WEB",
13
+ "name": "와이즈컬렉터3",
14
+ "url": "https://wc.nethru.co.kr",
15
+ "loggingId": "wc3",
16
+ "disabled": false
17
+ }]
18
+ }
19
+ };
1
20
  const mockSearchTaskByKeyword = {
2
21
  "type": "tool",
3
22
  "name": "search-task-by-keyword",
@@ -34,7 +53,390 @@ const mockSearchTaskByKeyword = {
34
53
  }]
35
54
  }
36
55
  };
37
- const mockSummary1 = {
56
+ const mockCollectSummary1 = {
57
+ "type": "tool",
58
+ "name": "query-summary-by-container-id",
59
+ "arguments": {
60
+ "containerId": "00001",
61
+ "from": "20251222",
62
+ "to": "20251222"
63
+ },
64
+ "result": {
65
+ "code": "SUCCESS",
66
+ "message": "",
67
+ "result": {
68
+ "input": 185,
69
+ "output": 85,
70
+ "filter": 50,
71
+ "error": 50
72
+ },
73
+ "queryTimestamp": "2025-12-22 15:06:20",
74
+ "cacheValidUntil": "2025-12-22 15:07:20"
75
+ }
76
+ };
77
+ const mockPrevTrend = {
78
+ "type": "tool",
79
+ "name": "query-trend-by-container-id",
80
+ "arguments": {
81
+ "containerId": "00001",
82
+ "from": "20251223",
83
+ "to": "20251223",
84
+ "unit": "60"
85
+ },
86
+ "result": {
87
+ "code": "SUCCESS",
88
+ "message": "",
89
+ "result": [{
90
+ "time": 1766415600000,
91
+ "metric": {
92
+ "input": 0,
93
+ "output": 0,
94
+ "filter": 0,
95
+ "error": 0
96
+ }
97
+ }, {
98
+ "time": 1766419200000,
99
+ "metric": {
100
+ "input": 0,
101
+ "output": 0,
102
+ "filter": 0,
103
+ "error": 0
104
+ }
105
+ }, {
106
+ "time": 1766422800000,
107
+ "metric": {
108
+ "input": 0,
109
+ "output": 0,
110
+ "filter": 0,
111
+ "error": 0
112
+ }
113
+ }, {
114
+ "time": 1766426400000,
115
+ "metric": {
116
+ "input": 0,
117
+ "output": 0,
118
+ "filter": 0,
119
+ "error": 0
120
+ }
121
+ }, {
122
+ "time": 1766430000000,
123
+ "metric": {
124
+ "input": 0,
125
+ "output": 0,
126
+ "filter": 0,
127
+ "error": 0
128
+ }
129
+ }, {
130
+ "time": 1766433600000,
131
+ "metric": {
132
+ "input": 0,
133
+ "output": 0,
134
+ "filter": 0,
135
+ "error": 0
136
+ }
137
+ }, {
138
+ "time": 1766437200000,
139
+ "metric": {
140
+ "input": 0,
141
+ "output": 0,
142
+ "filter": 0,
143
+ "error": 0
144
+ }
145
+ }, {
146
+ "time": 1766440800000,
147
+ "metric": {
148
+ "input": 0,
149
+ "output": 0,
150
+ "filter": 0,
151
+ "error": 0
152
+ }
153
+ }, {
154
+ "time": 1766444400000,
155
+ "metric": {
156
+ "input": 0,
157
+ "output": 0,
158
+ "filter": 0,
159
+ "error": 0
160
+ }
161
+ }, {
162
+ "time": 1766448000000,
163
+ "metric": {
164
+ "input": 21,
165
+ "output": 21,
166
+ "filter": 0,
167
+ "error": 0
168
+ }
169
+ }, {
170
+ "time": 1766451600000,
171
+ "metric": {
172
+ "input": 17,
173
+ "output": 17,
174
+ "filter": 0,
175
+ "error": 0
176
+ }
177
+ }, {
178
+ "time": 1766455200000,
179
+ "metric": {
180
+ "input": 7,
181
+ "output": 7,
182
+ "filter": 0,
183
+ "error": 0
184
+ }
185
+ }, {
186
+ "time": 1766458800000,
187
+ "metric": {
188
+ "input": 0,
189
+ "output": 0,
190
+ "filter": 0,
191
+ "error": 0
192
+ }
193
+ }, {
194
+ "time": 1766462400000,
195
+ "metric": {
196
+ "input": 0,
197
+ "output": 0,
198
+ "filter": 0,
199
+ "error": 0
200
+ }
201
+ }, {
202
+ "time": 1766466000000,
203
+ "metric": {
204
+ "input": 0,
205
+ "output": 0,
206
+ "filter": 0,
207
+ "error": 0
208
+ }
209
+ }, {
210
+ "time": 1766469600000,
211
+ "metric": {
212
+ "input": 23,
213
+ "output": 23,
214
+ "filter": 0,
215
+ "error": 0
216
+ }
217
+ }, {
218
+ "time": 1766473200000,
219
+ "metric": {
220
+ "input": 0,
221
+ "output": 0,
222
+ "filter": 0,
223
+ "error": 0
224
+ }
225
+ }, {
226
+ "time": 1766476800000,
227
+ "metric": {
228
+ "input": 0,
229
+ "output": 0,
230
+ "filter": 0,
231
+ "error": 0
232
+ }
233
+ }, {
234
+ "time": 1766480400000,
235
+ "metric": {
236
+ "input": 6,
237
+ "output": 6,
238
+ "filter": 0,
239
+ "error": 0
240
+ }
241
+ }, {
242
+ "time": 1766484000000,
243
+ "metric": {
244
+ "input": 0,
245
+ "output": 0,
246
+ "filter": 0,
247
+ "error": 0
248
+ }
249
+ }, {
250
+ "time": 1766487600000,
251
+ "metric": {
252
+ "input": 0,
253
+ "output": 0,
254
+ "filter": 0,
255
+ "error": 0
256
+ }
257
+ }, {
258
+ "time": 1766491200000,
259
+ "metric": {
260
+ "input": 0,
261
+ "output": 0,
262
+ "filter": 0,
263
+ "error": 0
264
+ }
265
+ }, {
266
+ "time": 1766494800000,
267
+ "metric": {
268
+ "input": 4,
269
+ "output": 4,
270
+ "filter": 0,
271
+ "error": 0
272
+ }
273
+ }, {
274
+ "time": 1766498400000,
275
+ "metric": {
276
+ "input": 0,
277
+ "output": 0,
278
+ "filter": 0,
279
+ "error": 0
280
+ }
281
+ }],
282
+ "queryTimestamp": "2025-12-24 16:25:36",
283
+ "cacheValidUntil": "2025-12-24 16:26:36"
284
+ }
285
+ };
286
+ const mockCurrentTrend = {
287
+ "type": "tool",
288
+ "name": "query-trend-by-container-id",
289
+ "arguments": {
290
+ "containerId": "00001",
291
+ "from": "20251224",
292
+ "to": "20251224",
293
+ "unit": "60"
294
+ },
295
+ "result": {
296
+ "code": "SUCCESS",
297
+ "message": "",
298
+ "result": [{
299
+ "time": 1766502000000,
300
+ "metric": {
301
+ "input": 0,
302
+ "output": 0,
303
+ "filter": 0,
304
+ "error": 0
305
+ }
306
+ }, {
307
+ "time": 1766505600000,
308
+ "metric": {
309
+ "input": 0,
310
+ "output": 0,
311
+ "filter": 0,
312
+ "error": 0
313
+ }
314
+ }, {
315
+ "time": 1766509200000,
316
+ "metric": {
317
+ "input": 0,
318
+ "output": 0,
319
+ "filter": 0,
320
+ "error": 0
321
+ }
322
+ }, {
323
+ "time": 1766512800000,
324
+ "metric": {
325
+ "input": 0,
326
+ "output": 0,
327
+ "filter": 0,
328
+ "error": 0
329
+ }
330
+ }, {
331
+ "time": 1766516400000,
332
+ "metric": {
333
+ "input": 0,
334
+ "output": 0,
335
+ "filter": 0,
336
+ "error": 0
337
+ }
338
+ }, {
339
+ "time": 1766520000000,
340
+ "metric": {
341
+ "input": 0,
342
+ "output": 0,
343
+ "filter": 0,
344
+ "error": 0
345
+ }
346
+ }, {
347
+ "time": 1766523600000,
348
+ "metric": {
349
+ "input": 0,
350
+ "output": 0,
351
+ "filter": 0,
352
+ "error": 0
353
+ }
354
+ }, {
355
+ "time": 1766527200000,
356
+ "metric": {
357
+ "input": 0,
358
+ "output": 0,
359
+ "filter": 0,
360
+ "error": 0
361
+ }
362
+ }, {
363
+ "time": 1766530800000,
364
+ "metric": {
365
+ "input": 0,
366
+ "output": 0,
367
+ "filter": 0,
368
+ "error": 0
369
+ }
370
+ }, {
371
+ "time": 1766534400000,
372
+ "metric": {
373
+ "input": 0,
374
+ "output": 0,
375
+ "filter": 0,
376
+ "error": 0
377
+ }
378
+ }, {
379
+ "time": 1766538000000,
380
+ "metric": {
381
+ "input": 0,
382
+ "output": 0,
383
+ "filter": 0,
384
+ "error": 0
385
+ }
386
+ }, {
387
+ "time": 1766541600000,
388
+ "metric": {
389
+ "input": 0,
390
+ "output": 0,
391
+ "filter": 0,
392
+ "error": 0
393
+ }
394
+ }, {
395
+ "time": 1766545200000,
396
+ "metric": {
397
+ "input": 0,
398
+ "output": 0,
399
+ "filter": 0,
400
+ "error": 0
401
+ }
402
+ }, {
403
+ "time": 1766548800000,
404
+ "metric": {
405
+ "input": 0,
406
+ "output": 0,
407
+ "filter": 0,
408
+ "error": 0
409
+ }
410
+ }, {
411
+ "time": 1766552400000,
412
+ "metric": {
413
+ "input": 60,
414
+ "output": 60,
415
+ "filter": 0,
416
+ "error": 0
417
+ }
418
+ }, {
419
+ "time": 1766556000000,
420
+ "metric": {
421
+ "input": 14,
422
+ "output": 14,
423
+ "filter": 0,
424
+ "error": 0
425
+ }
426
+ }, {
427
+ "time": 1766559600000,
428
+ "metric": {
429
+ "input": 13,
430
+ "output": 13,
431
+ "filter": 0,
432
+ "error": 0
433
+ }
434
+ }],
435
+ "queryTimestamp": "2025-12-24 16:25:37",
436
+ "cacheValidUntil": "2025-12-24 16:26:37"
437
+ }
438
+ };
439
+ const mockConvertSummary1 = {
38
440
  "type": "tool",
39
441
  "name": "query-summary-by-task-id",
40
442
  "arguments": {
@@ -59,7 +461,7 @@ const mockSummary1 = {
59
461
  "cacheValidUntil": "2025-12-04 09:34:48"
60
462
  }
61
463
  };
62
- const mockSummary2 = {
464
+ const mockConvertSummary2 = {
63
465
  "type": "tool",
64
466
  "name": "query-summary-by-task-id",
65
467
  "arguments": {
@@ -84,7 +486,7 @@ const mockSummary2 = {
84
486
  "cacheValidUntil": "2025-12-04 09:34:48"
85
487
  }
86
488
  };
87
- const mockTrendConvert1 = {
489
+ const mockConvertTrend1 = {
88
490
  "type": "tool",
89
491
  "name": "query-trend-by-task-id",
90
492
  "arguments": {
@@ -485,7 +887,7 @@ const mockTrendConvert1 = {
485
887
  "cacheValidUntil": "2025-12-11 13:08:30"
486
888
  }
487
889
  };
488
- const mockTrendConvert2 = {
890
+ const mockConvertTrend2 = {
489
891
  "type": "tool",
490
892
  "name": "query-trend-by-task-id",
491
893
  "arguments": {
@@ -718,32 +1120,33 @@ const mockTrendConvert2 = {
718
1120
  "cacheValidUntil": "2025-12-11 13:08:30"
719
1121
  }
720
1122
  };
1123
+ const sampleTable = {
1124
+ "type": "text",
1125
+ "value": "현재 등록된 컨테이너 목록은 아래와 같습니다 👇 \n\n| ID | 유형 | 이름 | URL | 활성화 여부 |\n|----|------|------|------|--------------|\n| 00001 | WEB | 와이즈컬렉터3 | [https://wc.nethru.co.kr](https://wc.nethru.co.kr) | ✅ 활성 |\n| 00003 | WEB | 넷스루 홈페이지 | [http://www.nethru.co.kr](http://www.nethru.co.kr) | ✅ 활성 |\n| 00004 | WEB | WC몰 | [https://wcmall.nethru.co.kr](https://wcmall.nethru.co.kr) | ✅ 활성 |\n| 00005 | WEB | Google Arts & Culture | [https://artsandculture.google.com](https://artsandculture.google.com) | ✅ 활성 |\n| 00006 | MOBILE | OmniNotes | [https://www.omnintes.com](https://www.omnintes.com) | ✅ 활성 |\n| 00007 | MOBILE | Plaid | [https://www.plaid.com](https://www.plaid.com) | ✅ 활성 |\n| 00008 | MOBILE | 안드로이드 샘플앱 | [http://www.androidtest.com](http://www.androidtest.com) | ✅ 활성 |\n| 00009 | MOBILE | 엄니노트 | [https://www.omnintes.com](https://www.omnintes.com) | ✅ 활성 |\n| 00026 | WEB | 신수민티스토리 | [https://nethoomru.tistory.com](https://nethoomru.tistory.com) | ✅ 활성 |\n| 00710 | WEB | solmin_test2 | [https://wc-solmin.tistory.com](https://wc-solmin.tistory.com) | ✅ 활성 |\n| 00711 | MOBILE | KB Pay | [https://kbpay.kbcard.com](https://kbpay.kbcard.com) | ✅ 활성 |\n| 00712 | MOBILE | OmniNotes2 | [https://www.omnintes.com](https://www.omnintes.com) | ✅ 활성 |\n| 00715 | WEB | test2 | [https://wc-solmin.tistory.com](https://wc-solmin.tistory.com) | ❌ 비활성 |\n| 00718 | MOBILE | iOS 샘플앱 | [http://www.iostest.com](http://www.iostest.com) | ✅ 활성 |\n| 00742 | MOBILE | 모바일 샘플앱 250903 (iOS 테스트 임시) | [http://www.androidtest.com](http://www.androidtest.com) | ✅ 활성 |\n| 00743 | MOBILE | 요기요 | [https://www.yogiyo.co.kr](https://www.yogiyo.co.kr) | ✅ 활성 |\n| 00744 | MOBILE | 사내 TFT 예제 - KBPay | [https://www.tftex1.co.kr](https://www.tftex1.co.kr) | ✅ 활성 |\n| 00745 | MOBILE | OmniNotes-soomsoom | [https://www.omnintes.com](https://www.omnintes.com) | ✅ 활성 |\n| 00746 | MOBILE | 믿고걸_뉴스 | [http://www.worthskippingnews.com](http://www.worthskippingnews.com) | ✅ 활성 |\n| 00747 | WEB | KB 은행 | [https://www.kbstar.com](https://www.kbstar.com) | ✅ 활성 |\n| 00748 | MOBILE | test251127 | [http://www.test251127.com](http://www.test251127.com) | ❌ 비활성 |\n| 00764 | MOBILE | test749 | [http://w.a.com](http://w.a.com) | ❌ 비활성 |\n| 00765 | MOBILE | test750 | [http://www.test.com](http://www.test.com) | ❌ 비활성 |\n\n총 **24개 컨테이너**가 등록되어 있으며, 이 중 **18개는 활성화**, **6개는 비활성화** 상태입니다. \n\n특정 컨테이너(예: “KB Pay”나 “와이즈컬렉터3”)의 **태스크 목록이나 설정 세부 정보**를 보고 싶으신가요?"
1126
+ };
1127
+ const sampleMarkdowns = {
1128
+ type: "text",
1129
+ value: "# Hello\n" + "https://example.org\n" + "```sh\n" + "# Code block\n" + "const func = () => {};\n" + "```\n" + "![Placeholder image](https://unsplash.it/600/400)\n" + "\n" + "~~strike~~ this\n" + "\n" + "[MIT](license) © [Titus Wormer](https://wooorm.com)\n" + "## TODO\n" + "\n" + "* [ ] This\n" + "* [ ] That\n" + "* [x] The other\n" + "\n" + "|Fahrenheit|Celsius|Kelvin|\n" + "|---:|---:|---:|\n" + "|-459.67|-273.15|0|\n" + "|-40|-40|233.15|\n" + "|32|0|273.15|\n" + "|212|100|373.15|\n"
1130
+ };
721
1131
  export const defaultMessages = [{
722
1132
  role: 'assistant',
723
1133
  // kinds: [],
724
- //kinds: ['COMPARE'],
1134
+ kinds: ['COMPARE'],
725
1135
  content: [
726
- // mockTrendConvert1,
727
- // {
728
- // ...mockTrendConvert1,
729
- // "arguments": {
730
- // "taskId": "wc_convert_2",
731
- // "from": "20251210",
732
- // "to": "20251210",
733
- // "unit": "30"
734
- // }
735
- // },
736
- // mockTrendConvert2,
737
- // mockSummary1,
738
- // mockSummary2,
739
- // mockSearchTaskByKeyword,
1136
+ // mockCollectSummary1,
1137
+ // mockConvertSummary1,
1138
+ // mockConvertSummary2,
1139
+
1140
+ // PeriodCompareChart
1141
+ mockPrevTrend, mockCurrentTrend,
1142
+ // StackedAreadTrendChart
1143
+ // mockConvertTrend1,
1144
+ // mockConvertTrend1,
1145
+
1146
+ mockSearchContainerByKeyword, mockSearchTaskByKeyword,
1147
+ // sampleTable,
1148
+ // sampleMarkdowns,
740
1149
  {
741
- "type": "text",
742
- "value": "현재 등록된 컨테이너 목록은 아래와 같습니다 👇 \n\n| ID | 유형 | 이름 | URL | 활성화 여부 |\n|----|------|------|------|--------------|\n| 00001 | WEB | 와이즈컬렉터3 | [https://wc.nethru.co.kr](https://wc.nethru.co.kr) | ✅ 활성 |\n| 00003 | WEB | 넷스루 홈페이지 | [http://www.nethru.co.kr](http://www.nethru.co.kr) | ✅ 활성 |\n| 00004 | WEB | WC몰 | [https://wcmall.nethru.co.kr](https://wcmall.nethru.co.kr) | ✅ 활성 |\n| 00005 | WEB | Google Arts & Culture | [https://artsandculture.google.com](https://artsandculture.google.com) | ✅ 활성 |\n| 00006 | MOBILE | OmniNotes | [https://www.omnintes.com](https://www.omnintes.com) | ✅ 활성 |\n| 00007 | MOBILE | Plaid | [https://www.plaid.com](https://www.plaid.com) | ✅ 활성 |\n| 00008 | MOBILE | 안드로이드 샘플앱 | [http://www.androidtest.com](http://www.androidtest.com) | ✅ 활성 |\n| 00009 | MOBILE | 엄니노트 | [https://www.omnintes.com](https://www.omnintes.com) | ✅ 활성 |\n| 00026 | WEB | 신수민티스토리 | [https://nethoomru.tistory.com](https://nethoomru.tistory.com) | ✅ 활성 |\n| 00710 | WEB | solmin_test2 | [https://wc-solmin.tistory.com](https://wc-solmin.tistory.com) | ✅ 활성 |\n| 00711 | MOBILE | KB Pay | [https://kbpay.kbcard.com](https://kbpay.kbcard.com) | ✅ 활성 |\n| 00712 | MOBILE | OmniNotes2 | [https://www.omnintes.com](https://www.omnintes.com) | ✅ 활성 |\n| 00715 | WEB | test2 | [https://wc-solmin.tistory.com](https://wc-solmin.tistory.com) | ❌ 비활성 |\n| 00718 | MOBILE | iOS 샘플앱 | [http://www.iostest.com](http://www.iostest.com) | ✅ 활성 |\n| 00742 | MOBILE | 모바일 샘플앱 250903 (iOS 테스트 임시) | [http://www.androidtest.com](http://www.androidtest.com) | ✅ 활성 |\n| 00743 | MOBILE | 요기요 | [https://www.yogiyo.co.kr](https://www.yogiyo.co.kr) | ✅ 활성 |\n| 00744 | MOBILE | 사내 TFT 예제 - KBPay | [https://www.tftex1.co.kr](https://www.tftex1.co.kr) | ✅ 활성 |\n| 00745 | MOBILE | OmniNotes-soomsoom | [https://www.omnintes.com](https://www.omnintes.com) | ✅ 활성 |\n| 00746 | MOBILE | 믿고걸_뉴스 | [http://www.worthskippingnews.com](http://www.worthskippingnews.com) | ✅ 활성 |\n| 00747 | WEB | KB 은행 | [https://www.kbstar.com](https://www.kbstar.com) | ✅ 활성 |\n| 00748 | MOBILE | test251127 | [http://www.test251127.com](http://www.test251127.com) | ❌ 비활성 |\n| 00764 | MOBILE | test749 | [http://w.a.com](http://w.a.com) | ❌ 비활성 |\n| 00765 | MOBILE | test750 | [http://www.test.com](http://www.test.com) | ❌ 비활성 |\n\n총 **24개 컨테이너**가 등록되어 있으며, 이 중 **18개는 활성화**, **6개는 비활성화** 상태입니다. \n\n특정 컨테이너(예: “KB Pay”나 “와이즈컬렉터3”)의 **태스크 목록이나 설정 세부 정보**를 보고 싶으신가요?"
743
- }, {
744
- type: "text",
745
- value: "# Hello\n" + "https://example.org\n" + "```sh\n" + "# Code block\n" + "const func = () => {};\n" + "```\n" + "![Placeholder image](https://unsplash.it/600/400)\n" + "\n" + "~~strike~~ this\n" + "\n" + "[MIT](license) © [Titus Wormer](https://wooorm.com)\n" + "## TODO\n" + "\n" + "* [ ] This\n" + "* [ ] That\n" + "* [x] The other\n" + "\n" + "|Fahrenheit|Celsius|Kelvin|\n" + "|---:|---:|---:|\n" + "|-459.67|-273.15|0|\n" + "|-40|-40|233.15|\n" + "|32|0|273.15|\n" + "|212|100|373.15|\n"
746
- }, {
747
1150
  type: 'text',
748
1151
  value: '안녕하세요! 저는 회사 내부 정보와 다양한 기능에 접근할 수 있는 AI 어시스턴트입니다.\n무엇을 도와드릴까요?'
749
1152
  }]
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@ export { default as AiChat } from './components/chat/AiChat';
3
3
  export { ChatProvider, useChatContext } from './components/chat/contexts/ChatContext';
4
4
  export { default as PieChart } from './components/charts/PieChart';
5
5
  export { default as ColumnChart } from './components/charts/ColumnChart';
6
- export { default as StackedAreaTrendChart } from './components/charts/StackedAreaTrendChart';
6
+ export { default as StackedAreaTrendChart } from './components/charts/StackedAreaTrendChart';
7
+ export { default as PeriodCompareChart } from './components/charts/PeriodCompareChart';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nethru/kit",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "A React component library by Nethru",
5
5
  "main": "dist/index.js",
6
6
  "homepage": ".",