@nethru/kit 1.0.7 → 1.1.1
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/charts/Chart.js +379 -0
- package/dist/components/charts/ChartColors.js +11 -0
- package/dist/components/charts/ColumnChart.js +118 -0
- package/dist/components/charts/Number.js +48 -0
- package/dist/components/charts/PieChart.js +168 -0
- package/dist/components/charts/StackedAreaTrendChart.js +52 -0
- package/dist/components/charts/TrendChart.js +30 -0
- package/dist/components/chat/AiChat.js +46 -0
- package/dist/components/chat/ChatInput.js +112 -0
- package/dist/components/chat/ChatMessage.js +76 -0
- package/dist/components/chat/ChatMessages.js +34 -0
- package/dist/components/chat/LoadingIndicator.js +48 -0
- package/dist/components/chat/content/MarkdownContent.css +151 -0
- package/dist/components/chat/content/MarkdownContent.js +14 -0
- package/dist/components/chat/content/ToolContent.js +86 -0
- package/dist/components/chat/content/wisecollector/ColumnChartContent.js +65 -0
- package/dist/components/chat/content/wisecollector/PieChartContent.js +89 -0
- package/dist/components/chat/content/wisecollector/StackedAreaChartContent.js +98 -0
- package/dist/components/chat/contexts/ToolContext.js +31 -0
- package/dist/components/chat/hooks/useChatApi.js +90 -0
- package/dist/components/chat/mock.js +737 -0
- package/dist/index.js +5 -22324
- package/dist/js/config.js +12 -0
- package/dist/js/dateHelper.js +78 -0
- package/package.json +17 -23
- package/dist/index.esm.js +0 -22317
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
.markdown-content {
|
|
2
|
+
white-space: normal;
|
|
3
|
+
word-wrap: break-word;
|
|
4
|
+
line-height: 1.6;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.markdown-content p {
|
|
8
|
+
margin: 0 0 0.75rem 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.markdown-content p:last-child {
|
|
12
|
+
margin-bottom: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.markdown-content h1,
|
|
16
|
+
.markdown-content h2,
|
|
17
|
+
.markdown-content h3,
|
|
18
|
+
.markdown-content h4,
|
|
19
|
+
.markdown-content h5,
|
|
20
|
+
.markdown-content h6 {
|
|
21
|
+
margin: 1rem 0 0.5rem 0;
|
|
22
|
+
font-weight: 600;
|
|
23
|
+
line-height: 1.3;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.markdown-content h1:first-child,
|
|
27
|
+
.markdown-content h2:first-child,
|
|
28
|
+
.markdown-content h3:first-child {
|
|
29
|
+
margin-top: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.markdown-content h1 {
|
|
33
|
+
font-size: 1.5em;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.markdown-content h2 {
|
|
37
|
+
font-size: 1.3em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.markdown-content h3 {
|
|
41
|
+
font-size: 1.15em;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.markdown-content h4 {
|
|
45
|
+
font-size: 1.05em;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.markdown-content ul,
|
|
49
|
+
.markdown-content ol {
|
|
50
|
+
margin: 0.5rem 0;
|
|
51
|
+
padding-left: 1.5rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.markdown-content li {
|
|
55
|
+
margin: 0.25rem 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.markdown-content code {
|
|
59
|
+
background: rgba(0, 0, 0, 0.05);
|
|
60
|
+
padding: 0.15rem 0.3rem;
|
|
61
|
+
border-radius: 0.25rem;
|
|
62
|
+
font-family: 'Courier New', monospace;
|
|
63
|
+
font-size: 0.9em;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.message.user .markdown-content code {
|
|
67
|
+
background: rgba(255, 255, 255, 0.2);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.markdown-content pre {
|
|
71
|
+
background: rgba(0, 0, 0, 0.05);
|
|
72
|
+
padding: 0.75rem;
|
|
73
|
+
border-radius: 0.375rem;
|
|
74
|
+
overflow-x: auto;
|
|
75
|
+
margin: 0.5rem 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.markdown-content pre code {
|
|
79
|
+
background: transparent;
|
|
80
|
+
padding: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.message.user .markdown-content pre {
|
|
84
|
+
background: rgba(255, 255, 255, 0.15);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.markdown-content strong {
|
|
88
|
+
font-weight: 700;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.markdown-content em {
|
|
92
|
+
font-style: italic;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.markdown-content a {
|
|
96
|
+
color: #667eea;
|
|
97
|
+
text-decoration: underline;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.message.user .markdown-content a {
|
|
101
|
+
color: rgba(255, 255, 255, 0.95);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.markdown-content blockquote {
|
|
105
|
+
border-left: 3px solid #667eea;
|
|
106
|
+
padding-left: 1rem;
|
|
107
|
+
margin: 0.75rem 0;
|
|
108
|
+
color: #666;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.message.user .markdown-content blockquote {
|
|
112
|
+
border-left-color: rgba(255, 255, 255, 0.5);
|
|
113
|
+
color: rgba(255, 255, 255, 0.9);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.markdown-content hr {
|
|
117
|
+
border: none;
|
|
118
|
+
border-top: 1px solid #e5e7eb;
|
|
119
|
+
margin: 1rem 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.message.user .markdown-content hr {
|
|
123
|
+
border-top-color: rgba(255, 255, 255, 0.3);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.markdown-content table {
|
|
127
|
+
border-collapse: collapse;
|
|
128
|
+
width: 100%;
|
|
129
|
+
margin: 0.75rem 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.markdown-content th,
|
|
133
|
+
.markdown-content td {
|
|
134
|
+
border: 1px solid #e5e7eb;
|
|
135
|
+
padding: 0.5rem;
|
|
136
|
+
text-align: left;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.markdown-content th {
|
|
140
|
+
background: rgba(0, 0, 0, 0.05);
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.message.user .markdown-content th,
|
|
145
|
+
.message.user .markdown-content td {
|
|
146
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.message.user .markdown-content th {
|
|
150
|
+
background: rgba(255, 255, 255, 0.15);
|
|
151
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ReactMarkdown from 'react-markdown';
|
|
2
|
+
import './MarkdownContent.css';
|
|
3
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
+
function MarkdownContent({
|
|
5
|
+
content
|
|
6
|
+
}) {
|
|
7
|
+
return /*#__PURE__*/_jsx("div", {
|
|
8
|
+
className: "markdown-content",
|
|
9
|
+
children: /*#__PURE__*/_jsx(ReactMarkdown, {
|
|
10
|
+
children: content
|
|
11
|
+
})
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export default MarkdownContent;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Box } from '@mui/material';
|
|
3
|
+
import { useToolContext } from "../contexts/ToolContext";
|
|
4
|
+
import ColumnChartContent from "./wisecollector/ColumnChartContent";
|
|
5
|
+
import StackedAreaChartContent from "./wisecollector/StackedAreaChartContent";
|
|
6
|
+
import PieChartContent from "./wisecollector/PieChartContent";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
8
|
+
export default function ToolContent() {
|
|
9
|
+
const {
|
|
10
|
+
kinds,
|
|
11
|
+
tools
|
|
12
|
+
} = useToolContext();
|
|
13
|
+
const hasCompare = kinds.includes('COMPARE');
|
|
14
|
+
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, {
|
|
21
|
+
tool: tool
|
|
22
|
+
}, index)), queryTrendTools.map((tools, index1) => {
|
|
23
|
+
if (hasCompare && tools.length >= 2) return /*#__PURE__*/_jsx(StackedAreaChartContent, {
|
|
24
|
+
tools: tools
|
|
25
|
+
}, index1);else {
|
|
26
|
+
return tools.map((tool, index2) => /*#__PURE__*/_jsx(StackedAreaChartContent, {
|
|
27
|
+
tools: [tool]
|
|
28
|
+
}, index2));
|
|
29
|
+
}
|
|
30
|
+
}), jsonTools.map((tool, index) => /*#__PURE__*/_jsxs(Box, {
|
|
31
|
+
className: "content-tool",
|
|
32
|
+
sx: styles.contentTool,
|
|
33
|
+
children: [/*#__PURE__*/_jsxs(Box, {
|
|
34
|
+
className: "tool-header",
|
|
35
|
+
sx: styles.toolHeader,
|
|
36
|
+
children: ["\uD83D\uDD27 ", tool.name]
|
|
37
|
+
}), /*#__PURE__*/_jsx(Box, {
|
|
38
|
+
component: "pre",
|
|
39
|
+
className: "tool-result",
|
|
40
|
+
sx: styles.toolResult,
|
|
41
|
+
children: JSON.stringify(tool.result?.result, null, 2)
|
|
42
|
+
})]
|
|
43
|
+
}, index))]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function groupBySamePeriod(tools) {
|
|
47
|
+
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}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const styles = {
|
|
63
|
+
contentTool: {
|
|
64
|
+
marginTop: '0.5rem',
|
|
65
|
+
padding: '0.75rem',
|
|
66
|
+
background: '#f1f3f5',
|
|
67
|
+
borderRadius: '6px',
|
|
68
|
+
borderLeft: '3px solid #666'
|
|
69
|
+
},
|
|
70
|
+
toolHeader: {
|
|
71
|
+
fontWeight: 600,
|
|
72
|
+
marginBottom: '0.5rem',
|
|
73
|
+
color: '#333',
|
|
74
|
+
fontSize: '0.9rem'
|
|
75
|
+
},
|
|
76
|
+
toolResult: {
|
|
77
|
+
background: 'white',
|
|
78
|
+
padding: '0.75rem',
|
|
79
|
+
borderRadius: '6px',
|
|
80
|
+
overflowX: 'auto',
|
|
81
|
+
fontSize: '0.75rem',
|
|
82
|
+
lineHeight: 1.4,
|
|
83
|
+
margin: 0,
|
|
84
|
+
color: '#333'
|
|
85
|
+
}
|
|
86
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useToolContext } from "../../contexts/ToolContext";
|
|
3
|
+
import { formatDateRange } from "../../../../js/dateHelper";
|
|
4
|
+
import ColumnChart from "../../../charts/ColumnChart";
|
|
5
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
6
|
+
const ColumnChartContent = ({
|
|
7
|
+
tools = []
|
|
8
|
+
}) => {
|
|
9
|
+
const {
|
|
10
|
+
configMap
|
|
11
|
+
} = useToolContext();
|
|
12
|
+
return /*#__PURE__*/_jsx(_Fragment, {
|
|
13
|
+
children: tools.length > 0 && /*#__PURE__*/_jsx("div", {
|
|
14
|
+
style: {
|
|
15
|
+
display: 'flex',
|
|
16
|
+
justifyContent: 'center',
|
|
17
|
+
marginBottom: '30px'
|
|
18
|
+
},
|
|
19
|
+
children: /*#__PURE__*/_jsx(ColumnChart, {
|
|
20
|
+
data: toData(tools, configMap)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
function toData(tools, configMap) {
|
|
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 => {
|
|
29
|
+
const {
|
|
30
|
+
taskId,
|
|
31
|
+
from,
|
|
32
|
+
to
|
|
33
|
+
} = content.arguments;
|
|
34
|
+
const item = {
|
|
35
|
+
name: `<b>${configMap.get(taskId)}</b> <span style="font-size:10px">(${formatDateRange(from, to)})</span>`,
|
|
36
|
+
data: []
|
|
37
|
+
};
|
|
38
|
+
const result = reduce(content.result.result);
|
|
39
|
+
item.data.push(result.input);
|
|
40
|
+
item.data.push(result.output);
|
|
41
|
+
if (hasConvert) item.data.push(result.filter);
|
|
42
|
+
item.data.push(result.error);
|
|
43
|
+
items.push(item);
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
colors: hasConvert ? ['#333333', '#2652a8', '#b47813', '#aa1a32'] : ['#333333', '#2652a8', '#aa1a32'],
|
|
47
|
+
categories: hasConvert ? ['입력', '처리', '필터', '에러'] : ['입력', '처리', '에러'],
|
|
48
|
+
series: items
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function reduce(data) {
|
|
52
|
+
return data.reduce((acc, item) => {
|
|
53
|
+
acc.input += item.metric.input;
|
|
54
|
+
acc.output += item.metric.output;
|
|
55
|
+
acc.filter += item.metric.filter;
|
|
56
|
+
acc.error += item.metric.error;
|
|
57
|
+
return acc;
|
|
58
|
+
}, {
|
|
59
|
+
input: 0,
|
|
60
|
+
output: 0,
|
|
61
|
+
filter: 0,
|
|
62
|
+
error: 0
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export default ColumnChartContent;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import PieChart from '../../../charts/PieChart';
|
|
3
|
+
import { useToolContext } from "../../contexts/ToolContext";
|
|
4
|
+
import { formatDateRange } from "../../../../js/dateHelper";
|
|
5
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
6
|
+
const PieChartContent = ({
|
|
7
|
+
tool
|
|
8
|
+
}) => {
|
|
9
|
+
const width = useMemo(() => {
|
|
10
|
+
return tool?.result?.result?.length < 3 ? 300 : undefined;
|
|
11
|
+
}, []);
|
|
12
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
13
|
+
children: [/*#__PURE__*/_jsx(Title, {
|
|
14
|
+
tool: tool
|
|
15
|
+
}), /*#__PURE__*/_jsx("div", {
|
|
16
|
+
style: {
|
|
17
|
+
display: 'flex',
|
|
18
|
+
justifyContent: 'center',
|
|
19
|
+
marginBottom: '30px'
|
|
20
|
+
},
|
|
21
|
+
children: toData(tool).map((item, index) => /*#__PURE__*/_jsx(PieChart, {
|
|
22
|
+
data: item,
|
|
23
|
+
width: width
|
|
24
|
+
}, index))
|
|
25
|
+
})]
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const Title = ({
|
|
29
|
+
tool
|
|
30
|
+
}) => {
|
|
31
|
+
const {
|
|
32
|
+
configMap
|
|
33
|
+
} = useToolContext();
|
|
34
|
+
const {
|
|
35
|
+
taskId,
|
|
36
|
+
from,
|
|
37
|
+
to
|
|
38
|
+
} = tool.arguments;
|
|
39
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
40
|
+
style: titleStyles,
|
|
41
|
+
children: [/*#__PURE__*/_jsx("div", {
|
|
42
|
+
children: configMap.get(taskId)
|
|
43
|
+
}), /*#__PURE__*/_jsxs("div", {
|
|
44
|
+
style: subtitleStyles,
|
|
45
|
+
children: ["(", formatDateRange(from, to), ")"]
|
|
46
|
+
})]
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
function toData(tool) {
|
|
50
|
+
const items = [];
|
|
51
|
+
tool?.result?.result?.forEach(agent => {
|
|
52
|
+
const {
|
|
53
|
+
output,
|
|
54
|
+
filter,
|
|
55
|
+
error
|
|
56
|
+
} = agent.metric;
|
|
57
|
+
const item = {
|
|
58
|
+
name: agent.name,
|
|
59
|
+
metrics: []
|
|
60
|
+
};
|
|
61
|
+
item.metrics.push({
|
|
62
|
+
label: '처리',
|
|
63
|
+
value: output,
|
|
64
|
+
transparent: output === 0
|
|
65
|
+
});
|
|
66
|
+
if (filter) {
|
|
67
|
+
item.metrics.push({
|
|
68
|
+
label: '처리',
|
|
69
|
+
value: filter
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
item.metrics.push({
|
|
73
|
+
label: '에러',
|
|
74
|
+
value: error
|
|
75
|
+
});
|
|
76
|
+
items.push(item);
|
|
77
|
+
});
|
|
78
|
+
return items;
|
|
79
|
+
}
|
|
80
|
+
const titleStyles = {
|
|
81
|
+
fontSize: '14px',
|
|
82
|
+
fontWeight: '500',
|
|
83
|
+
textAlign: 'center'
|
|
84
|
+
};
|
|
85
|
+
const subtitleStyles = {
|
|
86
|
+
fontSize: '10px',
|
|
87
|
+
fontWeight: '100'
|
|
88
|
+
};
|
|
89
|
+
export default PieChartContent;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useToolContext } from "../../contexts/ToolContext";
|
|
3
|
+
import { isOneDay, isToday } from "../../../../js/dateHelper";
|
|
4
|
+
import StackedAreaTrendChart from "../../../charts/StackedAreaTrendChart";
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
export default function StackedAreaChartContent({
|
|
7
|
+
tools
|
|
8
|
+
}) {
|
|
9
|
+
const {
|
|
10
|
+
configMap
|
|
11
|
+
} = useToolContext();
|
|
12
|
+
return /*#__PURE__*/_jsx("div", {
|
|
13
|
+
children: /*#__PURE__*/_jsx(StackedAreaTrendChart, {
|
|
14
|
+
metrics: toMetrics(tools, configMap),
|
|
15
|
+
records: fillRecords(tools, toRecords(tools)),
|
|
16
|
+
isDaily: isDaily(tools),
|
|
17
|
+
xPlotIndex: xPlotIndex(tools),
|
|
18
|
+
xAxisTickInterval: 4,
|
|
19
|
+
height: 200
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function toMetrics(tools, configMap) {
|
|
24
|
+
return tools.map(tool => ({
|
|
25
|
+
id: tool.arguments.taskId,
|
|
26
|
+
name: configMap.get(tool.arguments.taskId),
|
|
27
|
+
type: 'NUMBER'
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
function toRecords(tools) {
|
|
31
|
+
const recordMap = new Map();
|
|
32
|
+
tools.forEach(tool => {
|
|
33
|
+
const {
|
|
34
|
+
taskId
|
|
35
|
+
} = tool.arguments;
|
|
36
|
+
const results = tool?.result?.result || [];
|
|
37
|
+
results.forEach(result => {
|
|
38
|
+
const {
|
|
39
|
+
time
|
|
40
|
+
} = result;
|
|
41
|
+
if (!recordMap.has(time)) recordMap.set(time, {
|
|
42
|
+
time: time,
|
|
43
|
+
metric: {}
|
|
44
|
+
});
|
|
45
|
+
recordMap.get(time).metric[taskId] = result.metric.output;
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
return Array.from(recordMap.values()).sort((a, b) => a.time - b.time);
|
|
49
|
+
}
|
|
50
|
+
function fillRecords(tools, records) {
|
|
51
|
+
const {
|
|
52
|
+
from,
|
|
53
|
+
to,
|
|
54
|
+
unit
|
|
55
|
+
} = tools[0].arguments;
|
|
56
|
+
if (!records || records.length === 0 || !isToday(from, to)) return records;
|
|
57
|
+
const lastRecord = records[records.length - 1];
|
|
58
|
+
const lastTime = lastRecord.time;
|
|
59
|
+
const year = parseInt(to.substring(0, 4));
|
|
60
|
+
const month = parseInt(to.substring(4, 6)) - 1;
|
|
61
|
+
const day = parseInt(to.substring(6, 8));
|
|
62
|
+
const toDate = new Date(year, month, day);
|
|
63
|
+
const nextDay = new Date(toDate.getTime() + 24 * 60 * 60 * 1000);
|
|
64
|
+
const midnightStr = `${nextDay.getFullYear()}-${String(nextDay.getMonth() + 1).padStart(2, '0')}-${String(nextDay.getDate()).padStart(2, '0')}T00:00:00+09:00`;
|
|
65
|
+
const midnightUTC = new Date(midnightStr).getTime();
|
|
66
|
+
const metricKeys = Object.keys(lastRecord.metric);
|
|
67
|
+
const unitMs = unit * 60 * 1000;
|
|
68
|
+
const result = [...records];
|
|
69
|
+
for (let time = lastTime + unitMs; time < midnightUTC; time += unitMs) {
|
|
70
|
+
const metric = {};
|
|
71
|
+
metricKeys.forEach(key => {
|
|
72
|
+
metric[key] = null;
|
|
73
|
+
});
|
|
74
|
+
result.push({
|
|
75
|
+
time,
|
|
76
|
+
metric
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
function isDaily(tools) {
|
|
82
|
+
const {
|
|
83
|
+
from,
|
|
84
|
+
to
|
|
85
|
+
} = tools[0].arguments;
|
|
86
|
+
return !isOneDay(from, to);
|
|
87
|
+
}
|
|
88
|
+
function xPlotIndex(tools) {
|
|
89
|
+
const tool = tools[0];
|
|
90
|
+
const {
|
|
91
|
+
from,
|
|
92
|
+
to
|
|
93
|
+
} = tools[0].arguments;
|
|
94
|
+
if (isToday(from, to)) {
|
|
95
|
+
const length = tool?.result?.result?.length;
|
|
96
|
+
return length !== undefined ? length - 1 : undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo } from "react";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
const ToolContext = /*#__PURE__*/createContext();
|
|
4
|
+
export function ToolContextProvider({
|
|
5
|
+
message,
|
|
6
|
+
children
|
|
7
|
+
}) {
|
|
8
|
+
const kinds = useMemo(() => message.kinds ?? [], [message]);
|
|
9
|
+
const tools = useMemo(() => message.content.filter(c => c.type === 'tool'), [message]);
|
|
10
|
+
const configMap = useMemo(() => makeTaskConfigs(tools), [tools]);
|
|
11
|
+
return /*#__PURE__*/_jsx(ToolContext.Provider, {
|
|
12
|
+
value: {
|
|
13
|
+
kinds: kinds,
|
|
14
|
+
tools,
|
|
15
|
+
configMap
|
|
16
|
+
},
|
|
17
|
+
children: children
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function useToolContext() {
|
|
21
|
+
return useContext(ToolContext);
|
|
22
|
+
}
|
|
23
|
+
function makeTaskConfigs(tools) {
|
|
24
|
+
const map = new Map();
|
|
25
|
+
tools.filter(t => t.name === 'search-task-by-keyword').forEach(tool => {
|
|
26
|
+
tool.result?.result?.forEach(item => {
|
|
27
|
+
map.set(item.taskId, item.name);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
return map;
|
|
31
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { getConfig } from "../../../js/config";
|
|
3
|
+
const useChatApi = defaultMessages => {
|
|
4
|
+
const [messages, setMessages] = useState(defaultMessages ? defaultMessages : []);
|
|
5
|
+
const [inputValue, setInputValue] = useState('');
|
|
6
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
7
|
+
const [conversationId, setConversationId] = useState(null);
|
|
8
|
+
const [tools, setTools] = useState([]);
|
|
9
|
+
const chatContainerRef = useRef(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
loadTools();
|
|
12
|
+
}, []);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
scrollToBottom();
|
|
15
|
+
}, [messages]);
|
|
16
|
+
const scrollToBottom = () => {
|
|
17
|
+
if (chatContainerRef.current) {
|
|
18
|
+
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const loadTools = async () => {
|
|
22
|
+
const {
|
|
23
|
+
apiUrl
|
|
24
|
+
} = getConfig();
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(`${apiUrl}/api/mcp/tools`);
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
setTools(data.tools);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Error loading tools:', error);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const sendMessage = async () => {
|
|
34
|
+
const {
|
|
35
|
+
apiUrl
|
|
36
|
+
} = getConfig();
|
|
37
|
+
const message = inputValue.trim();
|
|
38
|
+
if (!message) return;
|
|
39
|
+
const userMessage = {
|
|
40
|
+
role: 'user',
|
|
41
|
+
content: [{
|
|
42
|
+
type: 'text',
|
|
43
|
+
value: message
|
|
44
|
+
}]
|
|
45
|
+
};
|
|
46
|
+
setMessages(prev => [...prev, userMessage]);
|
|
47
|
+
setInputValue('');
|
|
48
|
+
setIsLoading(true);
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(`${apiUrl}/api/chat`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: {
|
|
53
|
+
'Content-Type': 'application/json'
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
message: message,
|
|
57
|
+
conversationId: conversationId
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
setConversationId(data.conversationId);
|
|
62
|
+
setMessages(prev => [...prev, {
|
|
63
|
+
role: data.role,
|
|
64
|
+
kinds: data.kinds,
|
|
65
|
+
content: data.content
|
|
66
|
+
}]);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('Error:', error);
|
|
69
|
+
setMessages(prev => [...prev, {
|
|
70
|
+
role: 'assistant',
|
|
71
|
+
content: [{
|
|
72
|
+
type: 'text',
|
|
73
|
+
value: '죄송합니다. 오류가 발생했습니다.'
|
|
74
|
+
}]
|
|
75
|
+
}]);
|
|
76
|
+
} finally {
|
|
77
|
+
setIsLoading(false);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
messages,
|
|
82
|
+
inputValue,
|
|
83
|
+
setInputValue,
|
|
84
|
+
isLoading,
|
|
85
|
+
tools,
|
|
86
|
+
chatContainerRef,
|
|
87
|
+
sendMessage
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
export default useChatApi;
|