@nethru/kit 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,87 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { getConfig } from "../../../js/config";
3
+ const {
4
+ apiUrl
5
+ } = getConfig();
6
+ const useChatApi = defaultMessages => {
7
+ const [messages, setMessages] = useState(defaultMessages ? defaultMessages : []);
8
+ const [inputValue, setInputValue] = useState('');
9
+ const [isLoading, setIsLoading] = useState(false);
10
+ const [conversationId, setConversationId] = useState(null);
11
+ const [tools, setTools] = useState([]);
12
+ const chatContainerRef = useRef(null);
13
+ useEffect(() => {
14
+ loadTools();
15
+ }, []);
16
+ useEffect(() => {
17
+ scrollToBottom();
18
+ }, [messages]);
19
+ const scrollToBottom = () => {
20
+ if (chatContainerRef.current) {
21
+ chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
22
+ }
23
+ };
24
+ const loadTools = async () => {
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 message = inputValue.trim();
35
+ if (!message) return;
36
+ const userMessage = {
37
+ role: 'user',
38
+ content: [{
39
+ type: 'text',
40
+ value: message
41
+ }]
42
+ };
43
+ setMessages(prev => [...prev, userMessage]);
44
+ setInputValue('');
45
+ setIsLoading(true);
46
+ try {
47
+ const response = await fetch(`${apiUrl}/api/chat`, {
48
+ method: 'POST',
49
+ headers: {
50
+ 'Content-Type': 'application/json'
51
+ },
52
+ body: JSON.stringify({
53
+ message: message,
54
+ conversationId: conversationId
55
+ })
56
+ });
57
+ const data = await response.json();
58
+ setConversationId(data.conversationId);
59
+ setMessages(prev => [...prev, {
60
+ role: data.role,
61
+ kinds: data.kinds,
62
+ content: data.content
63
+ }]);
64
+ } catch (error) {
65
+ console.error('Error:', error);
66
+ setMessages(prev => [...prev, {
67
+ role: 'assistant',
68
+ content: [{
69
+ type: 'text',
70
+ value: '죄송합니다. 오류가 발생했습니다.'
71
+ }]
72
+ }]);
73
+ } finally {
74
+ setIsLoading(false);
75
+ }
76
+ };
77
+ return {
78
+ messages,
79
+ inputValue,
80
+ setInputValue,
81
+ isLoading,
82
+ tools,
83
+ chatContainerRef,
84
+ sendMessage
85
+ };
86
+ };
87
+ export default useChatApi;