@nethru/kit 1.1.9 → 1.1.10

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',
@@ -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',
@@ -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);
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,117 @@
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 { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ export default function PeriodCompareChartContent({
8
+ tools
9
+ }) {
10
+ const chartData = useMemo(() => toChartData(tools), [tools]);
11
+ return /*#__PURE__*/_jsxs("div", {
12
+ children: [/*#__PURE__*/_jsx(Title, {
13
+ tools: tools
14
+ }), /*#__PURE__*/_jsx(PeriodCompareChart, {
15
+ ...chartData,
16
+ height: 220
17
+ })]
18
+ });
19
+ }
20
+ function Title({
21
+ tools
22
+ }) {
23
+ const {
24
+ getName
25
+ } = useToolContext();
26
+ const {
27
+ containerId,
28
+ taskId
29
+ } = tools[0].arguments;
30
+ return /*#__PURE__*/_jsx("div", {
31
+ style: styles.title,
32
+ children: getName(containerId, taskId)
33
+ });
34
+ }
35
+ function toChartData(tools) {
36
+ const current = toSeries(tools[0]);
37
+ const prev = toSeries(tools[1]);
38
+ const currentSum = sum(current.records, 'output');
39
+ const records = prev.records.map((record, index) => ({
40
+ time: current.records[index]?.time ?? record.time,
41
+ diffTime: record.time,
42
+ metric: {
43
+ current: index < current.records.length ? current.records[index].metric.output : undefined,
44
+ prev: record.metric.output
45
+ }
46
+ }));
47
+ const averages = [{
48
+ label: `${current.unit}분 평균 (${current.period})`,
49
+ value: current.records.length ? currentSum / current.records.length : null
50
+ }, {
51
+ label: `${current.unit}분 평균 (${prev.period})`,
52
+ value: prev.records.length ? sum(prev.records, 'output') / prev.records.length : null
53
+ }];
54
+ return {
55
+ dimension: 'time',
56
+ metrics: [{
57
+ metric: 'current',
58
+ chartType: 'line'
59
+ }, {
60
+ metric: 'prev',
61
+ chartType: 'gradient'
62
+ }],
63
+ metas: {
64
+ time: {
65
+ name: '일시',
66
+ type: 'DATE'
67
+ },
68
+ current: {
69
+ name: current.period,
70
+ type: 'NUMBER',
71
+ pointerNames: current.isDaily && getPointerNames(current.records)
72
+ },
73
+ prev: {
74
+ name: prev.period,
75
+ type: 'NUMBER',
76
+ pointerNames: current.isDaily && getPointerNames(prev.records)
77
+ }
78
+ },
79
+ records: records,
80
+ averages: averages,
81
+ isDaily: current.isDaily,
82
+ xPlotIndex: current.records.length - 1,
83
+ secondaryXAxis: records.map(r => r.diffTime),
84
+ opacity: 1
85
+ };
86
+ }
87
+ function toSeries(tool) {
88
+ if (!tool) return {
89
+ records: [],
90
+ period: '',
91
+ unit: 30
92
+ };
93
+ const {
94
+ from,
95
+ to,
96
+ unit
97
+ } = tool.arguments;
98
+ return {
99
+ records: tool.result.result,
100
+ period: formatDateRange(from, to),
101
+ unit: Number(unit),
102
+ isDaily: !isOneDay(from, to)
103
+ };
104
+ }
105
+ function sum(records, field) {
106
+ return records.map(record => record.metric[field]).reduce((accumulator, current) => accumulator + current, 0);
107
+ }
108
+ function getPointerNames(records) {
109
+ return records.map(record => dayjs(record.time).format('YYYY-MM-DD'));
110
+ }
111
+ const styles = {
112
+ title: {
113
+ fontSize: '14px',
114
+ fontWeight: '500',
115
+ textAlign: 'center'
116
+ }
117
+ };
@@ -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,28 @@ 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 mockConvertSummary1 = {
38
78
  "type": "tool",
39
79
  "name": "query-summary-by-task-id",
40
80
  "arguments": {
@@ -59,7 +99,7 @@ const mockSummary1 = {
59
99
  "cacheValidUntil": "2025-12-04 09:34:48"
60
100
  }
61
101
  };
62
- const mockSummary2 = {
102
+ const mockConvertSummary2 = {
63
103
  "type": "tool",
64
104
  "name": "query-summary-by-task-id",
65
105
  "arguments": {
@@ -84,7 +124,408 @@ const mockSummary2 = {
84
124
  "cacheValidUntil": "2025-12-04 09:34:48"
85
125
  }
86
126
  };
87
- const mockTrendConvert1 = {
127
+ const mockCollectTrend1 = {
128
+ "type": "tool",
129
+ "name": "query-trend-by-container-id",
130
+ "arguments": {
131
+ "containerId": "00001",
132
+ "from": "20251210",
133
+ "to": "20251210",
134
+ "unit": "30"
135
+ },
136
+ "result": {
137
+ "code": "SUCCESS",
138
+ "message": "",
139
+ "result": [{
140
+ "time": 1765292400000,
141
+ "metric": {
142
+ "input": 0,
143
+ "output": 0,
144
+ "filter": 0,
145
+ "error": 0
146
+ }
147
+ }, {
148
+ "time": 1765294200000,
149
+ "metric": {
150
+ "input": 0,
151
+ "output": 0,
152
+ "filter": 0,
153
+ "error": 0
154
+ }
155
+ }, {
156
+ "time": 1765296000000,
157
+ "metric": {
158
+ "input": 0,
159
+ "output": 0,
160
+ "filter": 0,
161
+ "error": 0
162
+ }
163
+ }, {
164
+ "time": 1765297800000,
165
+ "metric": {
166
+ "input": 0,
167
+ "output": 0,
168
+ "filter": 0,
169
+ "error": 0
170
+ }
171
+ }, {
172
+ "time": 1765299600000,
173
+ "metric": {
174
+ "input": 0,
175
+ "output": 0,
176
+ "filter": 0,
177
+ "error": 0
178
+ }
179
+ }, {
180
+ "time": 1765301400000,
181
+ "metric": {
182
+ "input": 0,
183
+ "output": 0,
184
+ "filter": 0,
185
+ "error": 0
186
+ }
187
+ }, {
188
+ "time": 1765303200000,
189
+ "metric": {
190
+ "input": 0,
191
+ "output": 0,
192
+ "filter": 0,
193
+ "error": 0
194
+ }
195
+ }, {
196
+ "time": 1765305000000,
197
+ "metric": {
198
+ "input": 0,
199
+ "output": 0,
200
+ "filter": 0,
201
+ "error": 0
202
+ }
203
+ }, {
204
+ "time": 1765306800000,
205
+ "metric": {
206
+ "input": 0,
207
+ "output": 0,
208
+ "filter": 0,
209
+ "error": 0
210
+ }
211
+ }, {
212
+ "time": 1765308600000,
213
+ "metric": {
214
+ "input": 0,
215
+ "output": 0,
216
+ "filter": 0,
217
+ "error": 0
218
+ }
219
+ }, {
220
+ "time": 1765310400000,
221
+ "metric": {
222
+ "input": 0,
223
+ "output": 0,
224
+ "filter": 0,
225
+ "error": 0
226
+ }
227
+ }, {
228
+ "time": 1765312200000,
229
+ "metric": {
230
+ "input": 0,
231
+ "output": 0,
232
+ "filter": 0,
233
+ "error": 0
234
+ }
235
+ }, {
236
+ "time": 1765314000000,
237
+ "metric": {
238
+ "input": 0,
239
+ "output": 0,
240
+ "filter": 0,
241
+ "error": 0
242
+ }
243
+ }, {
244
+ "time": 1765315800000,
245
+ "metric": {
246
+ "input": 0,
247
+ "output": 0,
248
+ "filter": 0,
249
+ "error": 0
250
+ }
251
+ }, {
252
+ "time": 1765317600000,
253
+ "metric": {
254
+ "input": 0,
255
+ "output": 0,
256
+ "filter": 0,
257
+ "error": 0
258
+ }
259
+ }, {
260
+ "time": 1765319400000,
261
+ "metric": {
262
+ "input": 0,
263
+ "output": 0,
264
+ "filter": 0,
265
+ "error": 0
266
+ }
267
+ }, {
268
+ "time": 1765321200000,
269
+ "metric": {
270
+ "input": 0,
271
+ "output": 0,
272
+ "filter": 0,
273
+ "error": 0
274
+ }
275
+ }, {
276
+ "time": 1765323000000,
277
+ "metric": {
278
+ "input": 22,
279
+ "output": 22,
280
+ "filter": 0,
281
+ "error": 0
282
+ }
283
+ }, {
284
+ "time": 1765324800000,
285
+ "metric": {
286
+ "input": 16,
287
+ "output": 16,
288
+ "filter": 0,
289
+ "error": 0
290
+ }
291
+ }, {
292
+ "time": 1765326600000,
293
+ "metric": {
294
+ "input": 0,
295
+ "output": 0,
296
+ "filter": 0,
297
+ "error": 0
298
+ }
299
+ }, {
300
+ "time": 1765328400000,
301
+ "metric": {
302
+ "input": 32,
303
+ "output": 32,
304
+ "filter": 0,
305
+ "error": 0
306
+ }
307
+ }, {
308
+ "time": 1765330200000,
309
+ "metric": {
310
+ "input": 0,
311
+ "output": 0,
312
+ "filter": 0,
313
+ "error": 0
314
+ }
315
+ }, {
316
+ "time": 1765332000000,
317
+ "metric": {
318
+ "input": 0,
319
+ "output": 0,
320
+ "filter": 0,
321
+ "error": 0
322
+ }
323
+ }, {
324
+ "time": 1765333800000,
325
+ "metric": {
326
+ "input": 0,
327
+ "output": 0,
328
+ "filter": 0,
329
+ "error": 0
330
+ }
331
+ }, {
332
+ "time": 1765335600000,
333
+ "metric": {
334
+ "input": 0,
335
+ "output": 0,
336
+ "filter": 0,
337
+ "error": 0
338
+ }
339
+ }, {
340
+ "time": 1765337400000,
341
+ "metric": {
342
+ "input": 0,
343
+ "output": 0,
344
+ "filter": 0,
345
+ "error": 0
346
+ }
347
+ }, {
348
+ "time": 1765339200000,
349
+ "metric": {
350
+ "input": 0,
351
+ "output": 0,
352
+ "filter": 0,
353
+ "error": 0
354
+ }
355
+ }, {
356
+ "time": 1765341000000,
357
+ "metric": {
358
+ "input": 2,
359
+ "output": 2,
360
+ "filter": 0,
361
+ "error": 0
362
+ }
363
+ }, {
364
+ "time": 1765342800000,
365
+ "metric": {
366
+ "input": 0,
367
+ "output": 0,
368
+ "filter": 0,
369
+ "error": 0
370
+ }
371
+ }, {
372
+ "time": 1765344600000,
373
+ "metric": {
374
+ "input": 0,
375
+ "output": 0,
376
+ "filter": 0,
377
+ "error": 0
378
+ }
379
+ }, {
380
+ "time": 1765346400000,
381
+ "metric": {
382
+ "input": 0,
383
+ "output": 0,
384
+ "filter": 0,
385
+ "error": 0
386
+ }
387
+ }, {
388
+ "time": 1765348200000,
389
+ "metric": {
390
+ "input": 3,
391
+ "output": 3,
392
+ "filter": 0,
393
+ "error": 0
394
+ }
395
+ }, {
396
+ "time": 1765350000000,
397
+ "metric": {
398
+ "input": 0,
399
+ "output": 0,
400
+ "filter": 0,
401
+ "error": 0
402
+ }
403
+ }, {
404
+ "time": 1765351800000,
405
+ "metric": {
406
+ "input": 144,
407
+ "output": 144,
408
+ "filter": 0,
409
+ "error": 0
410
+ }
411
+ }, {
412
+ "time": 1765353600000,
413
+ "metric": {
414
+ "input": 240,
415
+ "output": 240,
416
+ "filter": 0,
417
+ "error": 0
418
+ }
419
+ }, {
420
+ "time": 1765355400000,
421
+ "metric": {
422
+ "input": 451,
423
+ "output": 451,
424
+ "filter": 0,
425
+ "error": 0
426
+ }
427
+ }, {
428
+ "time": 1765357200000,
429
+ "metric": {
430
+ "input": 559,
431
+ "output": 559,
432
+ "filter": 0,
433
+ "error": 0
434
+ }
435
+ }, {
436
+ "time": 1765359000000,
437
+ "metric": {
438
+ "input": 88,
439
+ "output": 88,
440
+ "filter": 0,
441
+ "error": 0
442
+ }
443
+ }, {
444
+ "time": 1765360800000,
445
+ "metric": {
446
+ "input": 26,
447
+ "output": 26,
448
+ "filter": 0,
449
+ "error": 0
450
+ }
451
+ }, {
452
+ "time": 1765362600000,
453
+ "metric": {
454
+ "input": 112,
455
+ "output": 112,
456
+ "filter": 0,
457
+ "error": 0
458
+ }
459
+ }, {
460
+ "time": 1765364400000,
461
+ "metric": {
462
+ "input": 86,
463
+ "output": 86,
464
+ "filter": 0,
465
+ "error": 0
466
+ }
467
+ }, {
468
+ "time": 1765366200000,
469
+ "metric": {
470
+ "input": 568,
471
+ "output": 568,
472
+ "filter": 0,
473
+ "error": 0
474
+ }
475
+ }, {
476
+ "time": 1765368000000,
477
+ "metric": {
478
+ "input": 623,
479
+ "output": 623,
480
+ "filter": 0,
481
+ "error": 0
482
+ }
483
+ }, {
484
+ "time": 1765369800000,
485
+ "metric": {
486
+ "input": 593,
487
+ "output": 593,
488
+ "filter": 0,
489
+ "error": 0
490
+ }
491
+ }, {
492
+ "time": 1765371600000,
493
+ "metric": {
494
+ "input": 415,
495
+ "output": 415,
496
+ "filter": 0,
497
+ "error": 0
498
+ }
499
+ }, {
500
+ "time": 1765373400000,
501
+ "metric": {
502
+ "input": 449,
503
+ "output": 449,
504
+ "filter": 0,
505
+ "error": 0
506
+ }
507
+ }, {
508
+ "time": 1765375200000,
509
+ "metric": {
510
+ "input": 168,
511
+ "output": 168,
512
+ "filter": 0,
513
+ "error": 0
514
+ }
515
+ }, {
516
+ "time": 1765377000000,
517
+ "metric": {
518
+ "input": 150,
519
+ "output": 150,
520
+ "filter": 0,
521
+ "error": 0
522
+ }
523
+ }],
524
+ "queryTimestamp": "2025-12-11 13:07:30",
525
+ "cacheValidUntil": "2025-12-11 13:08:30"
526
+ }
527
+ };
528
+ const mockConvertTrend1 = {
88
529
  "type": "tool",
89
530
  "name": "query-trend-by-task-id",
90
531
  "arguments": {
@@ -485,7 +926,7 @@ const mockTrendConvert1 = {
485
926
  "cacheValidUntil": "2025-12-11 13:08:30"
486
927
  }
487
928
  };
488
- const mockTrendConvert2 = {
929
+ const mockConvertTrend2 = {
489
930
  "type": "tool",
490
931
  "name": "query-trend-by-task-id",
491
932
  "arguments": {
@@ -718,32 +1159,43 @@ const mockTrendConvert2 = {
718
1159
  "cacheValidUntil": "2025-12-11 13:08:30"
719
1160
  }
720
1161
  };
1162
+ const sampleTable = {
1163
+ "type": "text",
1164
+ "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”)의 **태스크 목록이나 설정 세부 정보**를 보고 싶으신가요?"
1165
+ };
1166
+ const sampleMarkdowns = {
1167
+ type: "text",
1168
+ 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"
1169
+ };
721
1170
  export const defaultMessages = [{
722
1171
  role: 'assistant',
723
1172
  // kinds: [],
724
- //kinds: ['COMPARE'],
1173
+ kinds: ['COMPARE'],
725
1174
  content: [
726
- // mockTrendConvert1,
1175
+ // mockCollectSummary1,
1176
+ // mockConvertSummary1,
1177
+ // mockConvertSummary2,
1178
+
1179
+ // PeriodCompareChart
1180
+ // mockCollectTrend1,
727
1181
  // {
728
- // ...mockTrendConvert1,
1182
+ // ...mockCollectTrend1,
729
1183
  // "arguments": {
730
- // "taskId": "wc_convert_2",
731
- // "from": "20251210",
732
- // "to": "20251210",
1184
+ // "containerId": "00001",
1185
+ // "from": "20251209",
1186
+ // "to": "20251209",
733
1187
  // "unit": "30"
734
1188
  // }
735
1189
  // },
736
- // mockTrendConvert2,
737
- // mockSummary1,
738
- // mockSummary2,
739
- // mockSearchTaskByKeyword,
1190
+
1191
+ // StackedAreadTrendChart
1192
+ // mockConvertTrend1,
1193
+ // mockConvertTrend1,
1194
+
1195
+ mockSearchContainerByKeyword, mockSearchTaskByKeyword,
1196
+ // sampleTable,
1197
+ // sampleMarkdowns,
740
1198
  {
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
1199
  type: 'text',
748
1200
  value: '안녕하세요! 저는 회사 내부 정보와 다양한 기능에 접근할 수 있는 AI 어시스턴트입니다.\n무엇을 도와드릴까요?'
749
1201
  }]
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.10",
4
4
  "description": "A React component library by Nethru",
5
5
  "main": "dist/index.js",
6
6
  "homepage": ".",