@nine-lab/nine-mu 0.1.150 → 0.1.152
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/nine-mu.js +387 -6
- package/dist/nine-mu.js.map +1 -1
- package/dist/nine-mu.umd.js +3 -3
- package/dist/nine-mu.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChatMessage.js +487 -0
- package/src/components/NineChat.js +1 -1
- package/src/components/ai/aiContainer.js +438 -0
- package/src/components/ai/aiMessage.js +505 -0
- package/src/components/ai/aiSettings.js +149 -0
- package/src/index.js +1 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import ninegrid from "../index.js";
|
|
2
|
+
|
|
3
|
+
class aiMessage extends HTMLElement
|
|
4
|
+
{
|
|
5
|
+
#message;
|
|
6
|
+
#data;
|
|
7
|
+
#unique;
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.attachShadow({ mode: 'open' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
connectedCallback() {
|
|
15
|
+
this.#message = this.getAttribute("message");
|
|
16
|
+
|
|
17
|
+
this.shadowRoot.innerHTML = `
|
|
18
|
+
<style>
|
|
19
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid2@${ninegrid.version}/dist/css/ai.css";
|
|
20
|
+
${ninegrid.getCustomPath(this,"ai.css")}
|
|
21
|
+
</style>
|
|
22
|
+
|
|
23
|
+
<div class="chat-message">
|
|
24
|
+
<div class="message"></div>
|
|
25
|
+
<span class="more"><a href="#">more</a></span>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="grid"></div>
|
|
29
|
+
|
|
30
|
+
<div class="chat-menu">
|
|
31
|
+
<div class="left-menu">
|
|
32
|
+
<div class="filter"></div>
|
|
33
|
+
<div class="database"></div>
|
|
34
|
+
<div class="excel"></div>
|
|
35
|
+
<div class="maximize"></div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
`;
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
<svg class="filter" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
42
|
+
<path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2z"/>
|
|
43
|
+
</svg>
|
|
44
|
+
<svg class="database" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
45
|
+
<path d="M4.318 2.687C5.234 2.271 6.536 2 8 2s2.766.27 3.682.687C12.644 3.125 13 3.627 13 4c0 .374-.356.875-1.318 1.313C10.766 5.729 9.464 6 8 6s-2.766-.27-3.682-.687C3.356 4.875 3 4.373 3 4c0-.374.356-.875 1.318-1.313M13 5.698V7c0 .374-.356.875-1.318 1.313C10.766 8.729 9.464 9 8 9s-2.766-.27-3.682-.687C3.356 7.875 3 7.373 3 7V5.698c.271.202.58.378.904.525C4.978 6.711 6.427 7 8 7s3.022-.289 4.096-.777A5 5 0 0 0 13 5.698M14 4c0-1.007-.875-1.755-1.904-2.223C11.022 1.289 9.573 1 8 1s-3.022.289-4.096.777C2.875 2.245 2 2.993 2 4v9c0 1.007.875 1.755 1.904 2.223C4.978 15.71 6.427 16 8 16s3.022-.289 4.096-.777C13.125 14.755 14 14.007 14 13zm-1 4.698V10c0 .374-.356.875-1.318 1.313C10.766 11.729 9.464 12 8 12s-2.766-.27-3.682-.687C3.356 10.875 3 10.373 3 10V8.698c.271.202.58.378.904.525C4.978 9.71 6.427 10 8 10s3.022-.289 4.096-.777A5 5 0 0 0 13 8.698m0 3V13c0 .374-.356.875-1.318 1.313C10.766 14.729 9.464 15 8 15s-2.766-.27-3.682-.687C3.356 13.875 3 13.373 3 13v-1.302c.271.202.58.378.904.525C4.978 12.71 6.427 13 8 13s3.022-.289 4.096-.777c.324-.147.633-.323.904-.525"/>
|
|
46
|
+
</svg>
|
|
47
|
+
<svg class="excel" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
48
|
+
<path fill-rule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2h-1v-1h1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5zM6.472 15.29a1.2 1.2 0 0 1-.111-.449h.765a.58.58 0 0 0 .254.384q.106.073.25.114.143.041.319.041.246 0 .413-.07a.56.56 0 0 0 .255-.193.5.5 0 0 0 .085-.29.39.39 0 0 0-.153-.326q-.152-.12-.462-.193l-.619-.143a1.7 1.7 0 0 1-.539-.214 1 1 0 0 1-.351-.367 1.1 1.1 0 0 1-.123-.524q0-.366.19-.639.19-.272.527-.422.338-.15.777-.149.457 0 .78.152.324.153.5.41.18.255.2.566h-.75a.56.56 0 0 0-.12-.258.6.6 0 0 0-.247-.181.9.9 0 0 0-.369-.068q-.325 0-.513.152a.47.47 0 0 0-.184.384q0 .18.143.3a1 1 0 0 0 .405.175l.62.143q.326.075.566.211a1 1 0 0 1 .375.358q.135.222.135.56 0 .37-.188.656a1.2 1.2 0 0 1-.539.439q-.351.158-.858.158-.381 0-.665-.09a1.4 1.4 0 0 1-.478-.252 1.1 1.1 0 0 1-.29-.375m-2.945-3.358h-.893L1.81 13.37h-.036l-.832-1.438h-.93l1.227 1.983L0 15.931h.861l.853-1.415h.035l.85 1.415h.908L2.253 13.94zm2.727 3.325H4.557v-3.325h-.79v4h2.487z"/>
|
|
49
|
+
</svg>
|
|
50
|
+
<svg class="maximize" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
51
|
+
<path fill-rule="evenodd" d="M5.828 10.172a.5.5 0 0 0-.707 0l-4.096 4.096V11.5a.5.5 0 0 0-1 0v3.975a.5.5 0 0 0 .5.5H4.5a.5.5 0 0 0 0-1H1.732l4.096-4.096a.5.5 0 0 0 0-.707m4.344 0a.5.5 0 0 1 .707 0l4.096 4.096V11.5a.5.5 0 1 1 1 0v3.975a.5.5 0 0 1-.5.5H11.5a.5.5 0 0 1 0-1h2.768l-4.096-4.096a.5.5 0 0 1 0-.707m0-4.344a.5.5 0 0 0 .707 0l4.096-4.096V4.5a.5.5 0 1 0 1 0V.525a.5.5 0 0 0-.5-.5H11.5a.5.5 0 0 0 0 1h2.768l-4.096 4.096a.5.5 0 0 0 0 .707m-4.344 0a.5.5 0 0 1-.707 0L1.025 1.732V4.5a.5.5 0 0 1-1 0V.525a.5.5 0 0 1 .5-.5H4.5a.5.5 0 0 1 0 1H1.732l4.096 4.096a.5.5 0 0 1 0 .707"/>
|
|
52
|
+
</svg>
|
|
53
|
+
*/
|
|
54
|
+
requestAnimationFrame(() => {
|
|
55
|
+
const elMessage = this.shadowRoot.querySelector(".message");
|
|
56
|
+
elMessage.setAttribute("title", this.#message);
|
|
57
|
+
elMessage.innerHTML = this.#message.replaceAll("\n\n", "<br/>").replaceAll("\n", "<br/>");
|
|
58
|
+
|
|
59
|
+
this.shadowRoot.querySelector(".filter").addEventListener("click", e => {
|
|
60
|
+
|
|
61
|
+
//if (e.target.classList.contains("disabled")) return;
|
|
62
|
+
if (this.#unique) {
|
|
63
|
+
const grd = document.querySelector("nine-grid");
|
|
64
|
+
|
|
65
|
+
grd.filtering.set(this.#data.map(item =>
|
|
66
|
+
Object.fromEntries(this.#unique.map(key => [key, item[key]]))
|
|
67
|
+
));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.shadowRoot.querySelector(".database").addEventListener("click", e => {
|
|
72
|
+
//this.shadowRoot.querySelector("nine-grid").maximize();
|
|
73
|
+
const keys = this.#data.map(item =>
|
|
74
|
+
Object.fromEntries(this.#unique.map(key => [key, item[key]]))
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
console.log(keys);
|
|
78
|
+
|
|
79
|
+
this.dispatchEvent(new CustomEvent("database", { bubbles: true, detail: { key : keys } }));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.shadowRoot.querySelector(".maximize").addEventListener("click", e => {
|
|
83
|
+
this.shadowRoot.querySelector("nine-grid").maximize();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
this.shadowRoot.querySelector(".excel").addEventListener("click", e => {
|
|
87
|
+
this.shadowRoot.querySelector("nine-grid").export();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
this.shadowRoot.querySelector(".more a").addEventListener("click", () => {
|
|
91
|
+
alert(this.#message);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
const elMore = this.shadowRoot.querySelector(".more");
|
|
96
|
+
|
|
97
|
+
//if (ninegrid.isEllipsis(this.shadowRoot.querySelector(".message"))) {
|
|
98
|
+
if (this.#message.length > 50) {
|
|
99
|
+
elMore.style.display = "flex";
|
|
100
|
+
elMore.style.position = "absolute";
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
elMore.style.display = "none";
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
initialize = (info, data, unique) => {
|
|
110
|
+
this.#data = data;
|
|
111
|
+
this.#unique = unique;
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
if (!unique) {
|
|
116
|
+
this.shadowRoot.querySelector(".filter").classList.add("disabled");
|
|
117
|
+
this.shadowRoot.querySelector(".database").classList.add("disabled");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.#init(info);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#init = (info) => {
|
|
125
|
+
|
|
126
|
+
const rowCount = Number(this.getAttribute("row-count"));
|
|
127
|
+
if (rowCount < 1) {
|
|
128
|
+
this.shadowRoot.querySelector(".grid").style.display = "none";
|
|
129
|
+
this.shadowRoot.querySelector(".chat-menu").style.display = "none";
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let colgroup = info.map(() => `<col background-color="#888"/>`).join("");
|
|
134
|
+
let thead = info.map(item => `<th>${item.desc}</th>`).join("");
|
|
135
|
+
let tbody = info.map(item => `<td data-bind="${item.name}" text-align="${item.type == 'number' ? 'right' : 'left'}"></td>`).join("");
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
const html = `
|
|
139
|
+
<nine-grid class="ai" display-row-count="${rowCount > 5 ? 5 : rowCount}" show-title-bar="false" show-menu-icon="false" show-status-bar="false" select-type="row" auto-fit-col="true" show-title-bar="true" show-menu-icon="true" show-status-bar="true" enable-fixed-col="true" row-resizable="false" col-movable="true">
|
|
140
|
+
<table style="display:none;">
|
|
141
|
+
<colgroup>
|
|
142
|
+
<col background-color="#888" width="30" fixed="left" />
|
|
143
|
+
${colgroup}
|
|
144
|
+
</colgroup>
|
|
145
|
+
<thead>
|
|
146
|
+
<tr>
|
|
147
|
+
<th>No.</th>
|
|
148
|
+
${thead}
|
|
149
|
+
</tr>
|
|
150
|
+
</thead>
|
|
151
|
+
<tbody>
|
|
152
|
+
<tr>
|
|
153
|
+
<th><ng-row-indicator/></th>
|
|
154
|
+
${tbody}
|
|
155
|
+
</tr>
|
|
156
|
+
</tbody>
|
|
157
|
+
</table>
|
|
158
|
+
</nine-grid>
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
this.shadowRoot.querySelector(".grid").innerHTML = html;
|
|
162
|
+
|
|
163
|
+
setTimeout(() => {
|
|
164
|
+
const grd = this.shadowRoot.querySelector("nine-grid");
|
|
165
|
+
grd.data.source = this.#data;
|
|
166
|
+
|
|
167
|
+
//this.scrollIntoView({ behavior: "smooth", block: "end" });
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
class aiIngMessage extends HTMLElement
|
|
173
|
+
{
|
|
174
|
+
#message;
|
|
175
|
+
|
|
176
|
+
constructor() {
|
|
177
|
+
super();
|
|
178
|
+
this.attachShadow({ mode: 'open' });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
connectedCallback() {
|
|
182
|
+
//const message = this.getAttribute("message");
|
|
183
|
+
|
|
184
|
+
this.#message = ".";
|
|
185
|
+
|
|
186
|
+
this.shadowRoot.innerHTML = `
|
|
187
|
+
<style>
|
|
188
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid2@${ninegrid.version}/dist/css/ai.css";
|
|
189
|
+
${ninegrid.getCustomPath(this,"ai.css")}
|
|
190
|
+
</style>
|
|
191
|
+
|
|
192
|
+
<div class="chat-message">
|
|
193
|
+
.
|
|
194
|
+
</div>
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
this.#init();
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
#init = () => {
|
|
201
|
+
const interval = setInterval(() => {
|
|
202
|
+
this.#message = this.#message.length < 3 ? this.#message + "." : "";
|
|
203
|
+
this.shadowRoot.querySelector(".chat-message").innerHTML = this.#message || " ";
|
|
204
|
+
}, 500);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
class aiMyMessage extends HTMLElement
|
|
209
|
+
{
|
|
210
|
+
constructor() {
|
|
211
|
+
super();
|
|
212
|
+
this.attachShadow({ mode: 'open' });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
connectedCallback() {
|
|
216
|
+
const message = this.getAttribute("message");
|
|
217
|
+
|
|
218
|
+
this.shadowRoot.innerHTML = `
|
|
219
|
+
<style>
|
|
220
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid2@${ninegrid.version}/dist/css/ai.css";
|
|
221
|
+
${ninegrid.getCustomPath(this,"ai.css")}
|
|
222
|
+
</style>
|
|
223
|
+
|
|
224
|
+
<div class="chat-message">
|
|
225
|
+
${message}
|
|
226
|
+
</div>
|
|
227
|
+
`;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// aiProgressMessage 클래스 정의
|
|
232
|
+
// aiProgressMessage 클래스 정의 수정
|
|
233
|
+
class aiProgressMessage extends HTMLElement {
|
|
234
|
+
#progressData = [];
|
|
235
|
+
#progressElements = new Map();
|
|
236
|
+
#animationIntervals = new Map();
|
|
237
|
+
|
|
238
|
+
constructor() {
|
|
239
|
+
super();
|
|
240
|
+
this.attachShadow({ mode: 'open' });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
connectedCallback() {
|
|
244
|
+
this.shadowRoot.innerHTML = `
|
|
245
|
+
<style>
|
|
246
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid2@${ninegrid.version}/dist/css/ai.css";
|
|
247
|
+
${ninegrid.getCustomPath(this, "ai.css")}
|
|
248
|
+
</style>
|
|
249
|
+
<div class="chat-message progress-message">
|
|
250
|
+
<ul class="progress-list"></ul>
|
|
251
|
+
</div>
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 진행 상태 데이터를 초기화하고 화면에 표시합니다.
|
|
257
|
+
* @param {Array<Object>} data - [{id: 'step1', message: '분석중입니다.', completedMessage: '분석이 완료되었습니다.'}, ...]
|
|
258
|
+
*/
|
|
259
|
+
initialize(data) {
|
|
260
|
+
this.#progressData = data.map(item => ({
|
|
261
|
+
...item,
|
|
262
|
+
status: 'pending',
|
|
263
|
+
originalMessage: item.message
|
|
264
|
+
}));
|
|
265
|
+
setTimeout(() => {
|
|
266
|
+
this.#renderProgress(); // 초기 렌더링 시 현재 진행 단계를 활성화
|
|
267
|
+
this.#updateCurrentActiveProgress(); // 초기 활성 단계 설정
|
|
268
|
+
}, 300);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 특정 단계의 진행 상태를 업데이트합니다.
|
|
273
|
+
* @param {string} id - 업데이트할 단계의 고유 ID
|
|
274
|
+
* @param {string} status - 'completed' 또는 'pending'
|
|
275
|
+
*/
|
|
276
|
+
updateProgress(id, status) {
|
|
277
|
+
const itemIndex = this.#progressData.findIndex(item => item.id === id);
|
|
278
|
+
if (itemIndex > -1) {
|
|
279
|
+
if (this.#progressData[itemIndex].status !== status) {
|
|
280
|
+
this.#progressData[itemIndex].status = status;
|
|
281
|
+
this.#updateProgressItem(this.#progressData[itemIndex]); // 개별 항목 업데이트
|
|
282
|
+
this.#updateCurrentActiveProgress(); // 활성 진행 단계 재설정
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 현재 진행 중인 첫 번째 pending 항목을 찾아 'active' 상태로 만들고 애니메이션 시작
|
|
288
|
+
// 이전에 active였던 항목의 애니메이션은 중지
|
|
289
|
+
#updateCurrentActiveProgress() {
|
|
290
|
+
let foundActive = false;
|
|
291
|
+
this.#progressData.forEach(item => {
|
|
292
|
+
const li = this.#progressElements.get(item.id);
|
|
293
|
+
if (!li) return; // 요소가 아직 렌더링되지 않았으면 스킵
|
|
294
|
+
|
|
295
|
+
// 모든 항목에서 'active' 클래스 제거 및 애니메이션 중지
|
|
296
|
+
li.classList.remove('active');
|
|
297
|
+
if (this.#animationIntervals.has(item.id)) {
|
|
298
|
+
clearInterval(this.#animationIntervals.get(item.id));
|
|
299
|
+
this.#animationIntervals.delete(item.id);
|
|
300
|
+
li.querySelector(".animated-dots").textContent = ''; // 점 제거
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 첫 번째 pending 항목을 찾으면 'active'로 설정하고 애니메이션 시작
|
|
304
|
+
if (item.status === 'pending' && !foundActive) {
|
|
305
|
+
li.classList.add('active');
|
|
306
|
+
this.#startAnimation(item);
|
|
307
|
+
foundActive = true;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
#renderProgress() {
|
|
313
|
+
const progressList = this.shadowRoot.querySelector(".progress-list");
|
|
314
|
+
progressList.innerHTML = '';
|
|
315
|
+
this.#progressElements.clear();
|
|
316
|
+
this.#animationIntervals.forEach(intervalId => clearInterval(intervalId));
|
|
317
|
+
this.#animationIntervals.clear();
|
|
318
|
+
|
|
319
|
+
this.#progressData.forEach((item, index) => {
|
|
320
|
+
const li = document.createElement("li");
|
|
321
|
+
li.setAttribute("data-id", item.id);
|
|
322
|
+
li.classList.add("progress-item");
|
|
323
|
+
li.innerHTML = `
|
|
324
|
+
<span class="step-number">${index + 1}.</span>
|
|
325
|
+
<span class="text">${item.originalMessage}</span>
|
|
326
|
+
<span class="animated-dots"></span>
|
|
327
|
+
`;
|
|
328
|
+
progressList.appendChild(li);
|
|
329
|
+
this.#progressElements.set(item.id, li);
|
|
330
|
+
// 초기 렌더링 시에는 'active' 상태는 `#updateCurrentActiveProgress`에서 처리
|
|
331
|
+
this.#updateProgressItemVisuals(item); // 텍스트만 업데이트
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 항목의 시각적 상태(텍스트, 완료 체크)만 업데이트
|
|
336
|
+
#updateProgressItemVisuals(item) {
|
|
337
|
+
const li = this.#progressElements.get(item.id);
|
|
338
|
+
if (li) {
|
|
339
|
+
li.classList.remove('pending', 'completed');
|
|
340
|
+
li.classList.add(item.status);
|
|
341
|
+
|
|
342
|
+
const textSpan = li.querySelector(".text");
|
|
343
|
+
const dotsSpan = li.querySelector(".animated-dots");
|
|
344
|
+
|
|
345
|
+
if (item.status === 'completed') {
|
|
346
|
+
textSpan.textContent = item.completedMessage;
|
|
347
|
+
dotsSpan.textContent = ''; // 완료 시 점 제거
|
|
348
|
+
} else { // pending 상태
|
|
349
|
+
textSpan.textContent = item.originalMessage;
|
|
350
|
+
// pending이지만 active가 아닌 경우 점 없음 (CSS의 role)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// 점 애니메이션 시작 함수
|
|
356
|
+
#startAnimation(item) {
|
|
357
|
+
const li = this.#progressElements.get(item.id);
|
|
358
|
+
if (li) {
|
|
359
|
+
const dotsSpan = li.querySelector(".animated-dots");
|
|
360
|
+
let dotsCount = 0;
|
|
361
|
+
const intervalId = setInterval(() => {
|
|
362
|
+
dotsCount = (dotsCount + 1) % 4; // 0, 1, 2, 3 반복
|
|
363
|
+
dotsSpan.textContent = '.'.repeat(dotsCount);
|
|
364
|
+
}, 300);
|
|
365
|
+
this.#animationIntervals.set(item.id, intervalId);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// 개별 항목의 상태 업데이트 (텍스트 변경, 클래스 추가/제거)
|
|
370
|
+
// 애니메이션 시작/중지는 `#updateCurrentActiveProgress`에서 담당
|
|
371
|
+
#updateProgressItem(item) {
|
|
372
|
+
this.#updateProgressItemVisuals(item); // 텍스트와 클래스 업데이트
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class aiChat extends HTMLElement
|
|
379
|
+
{
|
|
380
|
+
constructor() {
|
|
381
|
+
super();
|
|
382
|
+
this.attachShadow({ mode: 'open' });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
connectedCallback() {
|
|
386
|
+
const message = this.getAttribute("message");
|
|
387
|
+
|
|
388
|
+
this.shadowRoot.innerHTML = `
|
|
389
|
+
<style>
|
|
390
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid2@${ninegrid.version}/dist/css/ai.css";
|
|
391
|
+
${ninegrid.getCustomPath(this,"ai.css")}
|
|
392
|
+
</style>
|
|
393
|
+
|
|
394
|
+
<div class="chat-body">
|
|
395
|
+
</div>
|
|
396
|
+
`;
|
|
397
|
+
|
|
398
|
+
this.#init();
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
#init = () => {
|
|
402
|
+
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
add = (sender, message, info, data, unique) => {
|
|
407
|
+
|
|
408
|
+
if (unique && !Array.isArray(unique)) {
|
|
409
|
+
unique = unique.split(",");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const target = this.shadowRoot.querySelector(".chat-body");
|
|
413
|
+
|
|
414
|
+
//console.log(data);
|
|
415
|
+
|
|
416
|
+
let el;
|
|
417
|
+
switch (sender) {
|
|
418
|
+
case "me":
|
|
419
|
+
el = document.createElement("nx-ai-my-message");
|
|
420
|
+
el.setAttribute("message", message);
|
|
421
|
+
target.appendChild(el);
|
|
422
|
+
break;
|
|
423
|
+
|
|
424
|
+
case "ing":
|
|
425
|
+
el = document.createElement("nx-ai-ing-message");
|
|
426
|
+
//el.setAttribute("message", message);
|
|
427
|
+
target.appendChild(el);
|
|
428
|
+
break;
|
|
429
|
+
|
|
430
|
+
case "ai":
|
|
431
|
+
this.shadowRoot.querySelectorAll("nx-ai-ing-message").forEach(el => {
|
|
432
|
+
el.remove();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
el = document.createElement("nx-ai-message");
|
|
436
|
+
el.setAttribute("message", message);
|
|
437
|
+
el.setAttribute("row-count", data ? data.length : 0);
|
|
438
|
+
//el.data = data;
|
|
439
|
+
el.initialize(info, data, unique);
|
|
440
|
+
target.appendChild(el);
|
|
441
|
+
break;
|
|
442
|
+
|
|
443
|
+
default:
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
setTimeout(() => {
|
|
448
|
+
el.scrollIntoView({ behavior: "smooth", block: "end" });
|
|
449
|
+
}, 200);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 챗 메시지를 추가합니다.
|
|
454
|
+
* @param {string} sender - 메시지 발신자 ('me', 'ing', 'ai', 'progress')
|
|
455
|
+
* @param {string} message - 메시지 내용 (progress 타입의 경우 사용되지 않음)
|
|
456
|
+
* @param {Array<Object>} info - nine-grid 정보 (ai 타입에만 해당)
|
|
457
|
+
* @param {Array<Object>} data - nine-grid 데이터 (ai 타입에만 해당)
|
|
458
|
+
* @param {Array<string>|string} unique - nine-grid 고유 키 (ai 타입에만 해당)
|
|
459
|
+
* @param {Array<Object>} progressData - aiProgressMessage의 초기 진행 데이터 (progress 타입에만 해당)
|
|
460
|
+
* @returns {HTMLElement|null} 생성된 메시지 엘리먼트 (특히 aiProgressMessage의 경우 업데이트를 위해 반환)
|
|
461
|
+
*/
|
|
462
|
+
addProgress = (progressData = null) => { // progressData 파라미터 추가
|
|
463
|
+
|
|
464
|
+
const target = this.shadowRoot.querySelector(".chat-body");
|
|
465
|
+
|
|
466
|
+
this.shadowRoot.querySelectorAll("nx-ai-ing-message").forEach(el => {
|
|
467
|
+
el.remove();
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
let el = document.createElement("nx-ai-progress-message");
|
|
471
|
+
if (progressData) {
|
|
472
|
+
el.initialize(progressData); // progressData로 초기화
|
|
473
|
+
}
|
|
474
|
+
target.appendChild(el);
|
|
475
|
+
|
|
476
|
+
setTimeout(() => {
|
|
477
|
+
if (el) { // el이 null이 아닐 때만 scrollIntoView 호출
|
|
478
|
+
el.scrollIntoView({ behavior: "smooth", block: "end" });
|
|
479
|
+
}
|
|
480
|
+
}, 200);
|
|
481
|
+
|
|
482
|
+
return el; // 생성된 엘리먼트를 반환
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
if (!customElements.get('nx-ai-message')) {
|
|
488
|
+
customElements.define("nx-ai-message", aiMessage);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (!customElements.get('nx-ai-ing-message')) {
|
|
492
|
+
customElements.define("nx-ai-ing-message", aiIngMessage);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!customElements.get('nx-ai-my-message')) {
|
|
496
|
+
customElements.define("nx-ai-my-message", aiMyMessage);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (!customElements.get('nx-ai-progress-message')) {
|
|
500
|
+
customElements.define("nx-ai-progress-message", aiProgressMessage);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!customElements.get('nx-ai-chat')) {
|
|
504
|
+
customElements.define("nx-ai-chat", aiChat);
|
|
505
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import ninegrid from "../index.js";
|
|
2
|
+
|
|
3
|
+
class aiSettings extends HTMLElement
|
|
4
|
+
{
|
|
5
|
+
constructor() {
|
|
6
|
+
console.log("construct");
|
|
7
|
+
|
|
8
|
+
super();
|
|
9
|
+
this.attachShadow({ mode: 'open' });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get geminiApiKey() { return this.shadowRoot.querySelector("#geminiApiKey").value; };
|
|
13
|
+
set geminiApiKey(v) { this.shadowRoot.querySelector("#geminiApiKey").value = v; };
|
|
14
|
+
|
|
15
|
+
get openaiApiKey() { return this.shadowRoot.querySelector("#openaiApiKey").value };
|
|
16
|
+
set openaiApiKey(v) { this.shadowRoot.querySelector("#openaiApiKey").value = v; };
|
|
17
|
+
|
|
18
|
+
get ollamaUrl() { return this.shadowRoot.querySelector("#ollamaUrl").value; };
|
|
19
|
+
set ollamaUrl(v) { this.shadowRoot.querySelector("#ollamaUrl").value = v; };
|
|
20
|
+
|
|
21
|
+
get qdrantUrl() { return this.shadowRoot.querySelector("#qdrantUrl").value; };
|
|
22
|
+
set qdrantUrl(v) { this.shadowRoot.querySelector("#qdrantUrl").value = v; };
|
|
23
|
+
|
|
24
|
+
get server() {
|
|
25
|
+
return this.shadowRoot.querySelector('input[name="server"]:checked')?.value;
|
|
26
|
+
};
|
|
27
|
+
set server(v) {
|
|
28
|
+
const elem = this.shadowRoot.querySelector(`input[name="server"][value="${v}"]`);
|
|
29
|
+
if (elem) elem.checked = true;
|
|
30
|
+
|
|
31
|
+
this.shadowRoot.querySelectorAll('input[name="server"]').forEach(el => {
|
|
32
|
+
el.dispatchEvent(new Event("change"));
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
get model() {
|
|
37
|
+
return this.shadowRoot.querySelector(`#${this.server}Model`)?.value;
|
|
38
|
+
};
|
|
39
|
+
set model(v) {
|
|
40
|
+
const elem = this.shadowRoot.querySelector(`#${this.server}Model`);
|
|
41
|
+
if (elem) {
|
|
42
|
+
elem.value = v;
|
|
43
|
+
elem.dispatchEvent(new Event("change"));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
this.dispatchEvent(new CustomEvent("setting-change", {
|
|
48
|
+
detail: { server: this.server, model: this.model }
|
|
49
|
+
})); */
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
connectedCallback() {
|
|
53
|
+
|
|
54
|
+
this.shadowRoot.innerHTML = `
|
|
55
|
+
<style>
|
|
56
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid2@${ninegrid.version}/dist/css/ai.css";
|
|
57
|
+
${ninegrid.getCustomPath(this,"ai.css")}
|
|
58
|
+
</style>
|
|
59
|
+
|
|
60
|
+
<div class="line subject">
|
|
61
|
+
<label for="server">Server:</label>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="line contents">
|
|
64
|
+
<label><input name="server" type="radio" value="gemini" checked>Gemini</label>
|
|
65
|
+
<label><input name="server" type="radio" value="openai">Open AI</label>
|
|
66
|
+
<label><input name="server" type="radio" value="ollama">Ollama</label>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="line gemini-bak" style="display:none;">
|
|
69
|
+
<label>API Key: <input id="geminiApiKey" value=""/></label>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="line openai-bak" style="display:none;">
|
|
72
|
+
<label>API Key: <input id="openaiApiKey" value=""/></label>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="line subject ollama">
|
|
75
|
+
<label for="ollamaUrl">Server Url:</label>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="line contents ollama">
|
|
78
|
+
<input type="text" id="ollamaUrl" value="http://localhost:11434"/></label>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="line subject ollama">
|
|
81
|
+
<label for="ollamaModel">Model:</label>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="line contents ollama">
|
|
84
|
+
<select id="ollamaModel">
|
|
85
|
+
<option value="phi4:14b">phi4:14b</option>
|
|
86
|
+
<option value="llama3.1:8b">llama3.1:8b</option>
|
|
87
|
+
<option value="mistral:7b">mistral:7b</option>
|
|
88
|
+
</select>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="line subject gemini">
|
|
91
|
+
<label for="geminiModel">Model:</label>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="line contents gemini">
|
|
94
|
+
<select id="geminiModel">
|
|
95
|
+
<option value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
|
|
96
|
+
<option value="gemini-pro">gemini-pro</option>
|
|
97
|
+
</select>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="line subject openai">
|
|
100
|
+
<label for="openaiModel">Model:</label>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="line contents openai">
|
|
103
|
+
<select id="openaiModel">
|
|
104
|
+
<option value="gpt-4">gpt-4</option>
|
|
105
|
+
<option value="gpt-4o">gpt-4o</option>
|
|
106
|
+
<option value="gpt-3.5-turbo">gpt-3.5-turbo</option>
|
|
107
|
+
</select>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div class="line subject qdrant">
|
|
111
|
+
<label for="qdrantUrl">Qdrant Url:</label>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="line contents qdrant">
|
|
114
|
+
<input type="text" id="qdrantUrl" value="http://localhost:6333"/>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div class="line subject analyze">
|
|
118
|
+
<input id="chkAnalyze" type="checkbox" checked> <input id="txtAnalyze" type="number" value="100"><label>건 미만인 경우 데이타 분석 요청</label>
|
|
119
|
+
</div>
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
this.#init();
|
|
123
|
+
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
this.server = "gemini";
|
|
126
|
+
}, 0);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
#init = () => {
|
|
132
|
+
|
|
133
|
+
this.shadowRoot.querySelectorAll('input[name="server"]').forEach((radio) => {
|
|
134
|
+
radio.addEventListener("change", () => {
|
|
135
|
+
const server = this.shadowRoot.querySelector('input[name="server"]:checked').value;
|
|
136
|
+
|
|
137
|
+
["gemini","openai","ollama"].forEach(v => {
|
|
138
|
+
this.shadowRoot.querySelectorAll('.' + v).forEach((elem) => {
|
|
139
|
+
elem.style.display = (server === v) ? "flex" : "none";
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!customElements.get('nx-ai-settings')) {
|
|
148
|
+
customElements.define("nx-ai-settings", aiSettings);
|
|
149
|
+
}
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { trace } from '@nine-lab/nine-util';
|
|
|
2
2
|
import { NineChat } from './components/NineChat.js';
|
|
3
3
|
import { NineDiff } from './components/NineDiff.js';
|
|
4
4
|
import { NineDiffPopup } from './components/NineDiffPopup.js';
|
|
5
|
+
import './components/ChatMessage.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Nine-Mu 엔진 메인 클래스
|