@dssp/project 1.0.0-alpha.38 → 1.0.0-alpha.40

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 @@
1
+ export * from './pages/lib/chatbot-widget';
@@ -1,2 +1,2 @@
1
- "use strict";
1
+ export * from './pages/lib/chatbot-widget';
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"","sourcesContent":[""]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA","sourcesContent":["export * from './pages/lib/chatbot-widget'\n"]}
@@ -0,0 +1,43 @@
1
+ import { LitElement } from 'lit-element';
2
+ interface ChatMessage {
3
+ id: string;
4
+ text: string;
5
+ isUser: boolean;
6
+ timestamp: Date;
7
+ }
8
+ export declare class ChatbotWidget extends LitElement {
9
+ static get properties(): {
10
+ isOpen: {
11
+ type: BooleanConstructor;
12
+ };
13
+ messages: {
14
+ type: ArrayConstructor;
15
+ };
16
+ inputValue: {
17
+ type: StringConstructor;
18
+ };
19
+ isLoading: {
20
+ type: BooleanConstructor;
21
+ };
22
+ };
23
+ isOpen: boolean;
24
+ messages: ChatMessage[];
25
+ inputValue: string;
26
+ isLoading: boolean;
27
+ private boundHandleEscapeKey;
28
+ connectedCallback(): void;
29
+ disconnectedCallback(): void;
30
+ handleEscapeKey(event: KeyboardEvent): void;
31
+ static styles: import("lit-element").CSSResult;
32
+ toggleChat(): void;
33
+ scrollToBottom(): void;
34
+ sendMessage(): Promise<void>;
35
+ handleKeyPress(event: KeyboardEvent): void;
36
+ render(): import("lit-html").TemplateResult<1>;
37
+ }
38
+ declare global {
39
+ interface HTMLElementTagNameMap {
40
+ 'chatbot-widget': ChatbotWidget;
41
+ }
42
+ }
43
+ export {};
@@ -0,0 +1,469 @@
1
+ import { LitElement, html, css } from 'lit-element';
2
+ // API 상수
3
+ const CHATBOT_API_URL = 'https://api.example.com/chatbot';
4
+ export class ChatbotWidget extends LitElement {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.isOpen = false;
8
+ this.messages = [];
9
+ this.inputValue = '';
10
+ this.isLoading = false;
11
+ this.boundHandleEscapeKey = this.handleEscapeKey.bind(this);
12
+ }
13
+ static get properties() {
14
+ return {
15
+ isOpen: { type: Boolean },
16
+ messages: { type: Array },
17
+ inputValue: { type: String },
18
+ isLoading: { type: Boolean }
19
+ };
20
+ }
21
+ connectedCallback() {
22
+ super.connectedCallback();
23
+ }
24
+ disconnectedCallback() {
25
+ super.disconnectedCallback();
26
+ // 컴포넌트가 제거될 때 이벤트 리스너 정리
27
+ document.removeEventListener('keydown', this.boundHandleEscapeKey);
28
+ }
29
+ handleEscapeKey(event) {
30
+ if (event.key === 'Escape' && this.isOpen) {
31
+ this.isOpen = false;
32
+ }
33
+ }
34
+ toggleChat() {
35
+ this.isOpen = !this.isOpen;
36
+ if (this.isOpen) {
37
+ // 챗봇이 열릴 때 ESC 키 이벤트 리스너 추가
38
+ document.addEventListener('keydown', this.boundHandleEscapeKey);
39
+ // 챗봇이 열릴 때 스크롤을 맨 아래로
40
+ this.requestUpdate();
41
+ setTimeout(() => this.scrollToBottom(), 100);
42
+ }
43
+ else {
44
+ // 챗봇이 닫힐 때 ESC 키 이벤트 리스너 제거
45
+ document.removeEventListener('keydown', this.boundHandleEscapeKey);
46
+ }
47
+ }
48
+ scrollToBottom() {
49
+ var _a;
50
+ const messagesContainer = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.messages-container');
51
+ if (messagesContainer) {
52
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
53
+ }
54
+ }
55
+ async sendMessage() {
56
+ var _a;
57
+ if (!this.inputValue.trim() || this.isLoading)
58
+ return;
59
+ const userMessage = {
60
+ id: Date.now().toString(),
61
+ text: this.inputValue.trim(),
62
+ isUser: true,
63
+ timestamp: new Date()
64
+ };
65
+ this.messages = [...this.messages, userMessage];
66
+ const messageText = this.inputValue.trim();
67
+ this.inputValue = '';
68
+ this.isLoading = true;
69
+ // 사용자 메시지 추가 후 스크롤을 맨 아래로
70
+ setTimeout(() => this.scrollToBottom(), 50);
71
+ // 포커스 유지를 위해 input 요소 참조 저장
72
+ const input = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.message-input');
73
+ try {
74
+ // API 호출
75
+ const response = await fetch(CHATBOT_API_URL, {
76
+ method: 'POST',
77
+ headers: {
78
+ 'Content-Type': 'application/json'
79
+ },
80
+ body: JSON.stringify({ message: messageText })
81
+ });
82
+ const data = await response.json();
83
+ const botMessage = {
84
+ id: (Date.now() + 1).toString(),
85
+ text: data.response || '죄송합니다. 응답을 처리할 수 없습니다.',
86
+ isUser: false,
87
+ timestamp: new Date()
88
+ };
89
+ this.messages = [...this.messages, botMessage];
90
+ // 봇 메시지 추가 후 스크롤을 맨 아래로
91
+ setTimeout(() => this.scrollToBottom(), 50);
92
+ }
93
+ catch (error) {
94
+ console.error('챗봇 API 오류:', error);
95
+ const errorMessage = {
96
+ id: (Date.now() + 1).toString(),
97
+ text: '죄송합니다. 일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요.',
98
+ isUser: false,
99
+ timestamp: new Date()
100
+ };
101
+ this.messages = [...this.messages, errorMessage];
102
+ // 에러 메시지 추가 후 스크롤을 맨 아래로
103
+ setTimeout(() => this.scrollToBottom(), 50);
104
+ }
105
+ finally {
106
+ this.isLoading = false;
107
+ // 로딩 완료 후 포커스 복원
108
+ setTimeout(() => {
109
+ if (input) {
110
+ input.focus();
111
+ }
112
+ }, 100);
113
+ }
114
+ }
115
+ handleKeyPress(event) {
116
+ if (event.key === 'Enter' && !event.shiftKey) {
117
+ event.preventDefault();
118
+ this.sendMessage();
119
+ }
120
+ }
121
+ render() {
122
+ return html `
123
+ <div class="chatbot-container">
124
+ <!-- 챗봇 아이콘 -->
125
+ <button class="chatbot-icon" @click="${this.toggleChat}">
126
+ <svg viewBox="0 0 24 24">
127
+ <path
128
+ d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"
129
+ />
130
+ </svg>
131
+ </button>
132
+
133
+ <!-- 대화창 -->
134
+ <div class="chat-window ${this.isOpen ? 'open' : ''}">
135
+ <!-- 헤더 -->
136
+ <div class="chat-header">
137
+ <h3>챗봇 도우미</h3>
138
+ <button class="close-btn" @click="${this.toggleChat}">×</button>
139
+ </div>
140
+
141
+ <!-- 메시지 영역 -->
142
+ <div class="messages-container">
143
+ ${this.messages.length === 0
144
+ ? html `
145
+ <div class="message bot">
146
+ <div class="message-avatar">🤖</div>
147
+ <div class="message-bubble">안녕하세요! 무엇을 도와드릴까요?</div>
148
+ </div>
149
+ `
150
+ : ''}
151
+ ${this.messages.map(message => html `
152
+ <div class="message ${message.isUser ? 'user' : 'bot'}">
153
+ <div class="message-avatar">${message.isUser ? '👤' : '🤖'}</div>
154
+ <div class="message-bubble">${message.text}</div>
155
+ </div>
156
+ `)}
157
+ ${this.isLoading
158
+ ? html `
159
+ <div class="message bot">
160
+ <div class="message-avatar">🤖</div>
161
+ <div class="message-bubble">
162
+ <div class="loading-indicator">
163
+ <span>답변 중</span>
164
+ <div class="loading-dots">
165
+ <div class="loading-dot"></div>
166
+ <div class="loading-dot"></div>
167
+ <div class="loading-dot"></div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ `
173
+ : ''}
174
+ </div>
175
+
176
+ <!-- 입력 영역 -->
177
+ <div class="input-container">
178
+ <input
179
+ class="message-input"
180
+ type="text"
181
+ placeholder="메시지를 입력하세요..."
182
+ .value="${this.inputValue}"
183
+ @input="${(e) => (this.inputValue = e.target.value)}"
184
+ @keypress="${this.handleKeyPress}"
185
+ ?disabled="${this.isLoading}"
186
+ />
187
+ <button class="send-btn" @click="${this.sendMessage}" ?disabled="${this.isLoading || !this.inputValue.trim()}">
188
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
189
+ <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
190
+ </svg>
191
+ </button>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ `;
196
+ }
197
+ }
198
+ ChatbotWidget.styles = css `
199
+ :host {
200
+ position: fixed;
201
+ bottom: 40px;
202
+ right: 20px;
203
+ z-index: 1000;
204
+ }
205
+
206
+ .chatbot-container {
207
+ position: relative;
208
+ }
209
+
210
+ .chatbot-icon {
211
+ width: 60px;
212
+ height: 60px;
213
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
214
+ border-radius: 50%;
215
+ display: flex;
216
+ align-items: center;
217
+ justify-content: center;
218
+ cursor: pointer;
219
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
220
+ transition: all 0.3s ease;
221
+ border: none;
222
+ outline: none;
223
+ }
224
+
225
+ .chatbot-icon:hover {
226
+ transform: scale(1.1);
227
+ box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2);
228
+ }
229
+
230
+ .chatbot-icon svg {
231
+ width: 28px;
232
+ height: 28px;
233
+ fill: white;
234
+ }
235
+
236
+ .chat-window {
237
+ position: absolute;
238
+ bottom: 80px;
239
+ right: 0;
240
+ width: 350px;
241
+ height: 500px;
242
+ background: white;
243
+ border-radius: 20px;
244
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
245
+ display: flex;
246
+ flex-direction: column;
247
+ overflow: hidden;
248
+ transform: translateY(20px) scale(0.95);
249
+ opacity: 0;
250
+ transition: all 0.3s ease;
251
+ border: 1px solid #e1e5e9;
252
+ pointer-events: none; /* 닫혀있을 때 클릭 차단 해제 */
253
+ }
254
+
255
+ .chat-window.open {
256
+ transform: translateY(0) scale(1);
257
+ opacity: 1;
258
+ pointer-events: auto; /* 열렸을 때 클릭 가능 */
259
+ }
260
+
261
+ .chat-header {
262
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
263
+ color: white;
264
+ padding: 15px 20px;
265
+ display: flex;
266
+ align-items: center;
267
+ justify-content: space-between;
268
+ }
269
+
270
+ .chat-header h3 {
271
+ margin: 0;
272
+ font-size: 18px;
273
+ font-weight: 600;
274
+ }
275
+
276
+ .close-btn {
277
+ background: none;
278
+ border: none;
279
+ color: white;
280
+ cursor: pointer;
281
+ font-size: 32px;
282
+ padding: 0;
283
+ width: 36px;
284
+ height: 36px;
285
+ display: flex;
286
+ align-items: center;
287
+ justify-content: center;
288
+ border-radius: 50%;
289
+ transition: background-color 0.2s ease;
290
+ }
291
+
292
+ .close-btn:hover {
293
+ background-color: rgba(255, 255, 255, 0.2);
294
+ }
295
+
296
+ .messages-container {
297
+ flex: 1;
298
+ overflow-y: auto;
299
+ padding: 20px;
300
+ display: flex;
301
+ flex-direction: column;
302
+ gap: 15px;
303
+ }
304
+
305
+ .message {
306
+ display: flex;
307
+ align-items: flex-end;
308
+ gap: 10px;
309
+ max-width: 80%;
310
+ }
311
+
312
+ .message.user {
313
+ align-self: flex-end;
314
+ flex-direction: row-reverse;
315
+ }
316
+
317
+ .message.bot {
318
+ align-self: flex-start;
319
+ }
320
+
321
+ .message-bubble {
322
+ padding: 12px 16px;
323
+ border-radius: 18px;
324
+ font-size: 14px;
325
+ line-height: 1.4;
326
+ word-wrap: break-word;
327
+ position: relative;
328
+ }
329
+
330
+ .message.user .message-bubble {
331
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
332
+ color: white;
333
+ border-bottom-right-radius: 4px;
334
+ }
335
+
336
+ .message.bot .message-bubble {
337
+ background: #f1f3f4;
338
+ color: #333;
339
+ border-bottom-left-radius: 4px;
340
+ }
341
+
342
+ .message-avatar {
343
+ width: 32px;
344
+ height: 32px;
345
+ border-radius: 50%;
346
+ display: flex;
347
+ align-items: center;
348
+ justify-content: center;
349
+ font-size: 12px;
350
+ font-weight: 600;
351
+ flex-shrink: 0;
352
+ }
353
+
354
+ .message.user .message-avatar {
355
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
356
+ color: white;
357
+ }
358
+
359
+ .message.bot .message-avatar {
360
+ background: #e8eaf0;
361
+ color: #666;
362
+ }
363
+
364
+ .input-container {
365
+ padding: 20px;
366
+ border-top: 1px solid #e1e5e9;
367
+ display: flex;
368
+ gap: 10px;
369
+ align-items: center;
370
+ }
371
+
372
+ .message-input {
373
+ flex: 1;
374
+ border: 1px solid #e1e5e9;
375
+ border-radius: 25px;
376
+ padding: 12px 16px;
377
+ font-size: 14px;
378
+ outline: none;
379
+ transition: border-color 0.2s ease;
380
+ }
381
+
382
+ .message-input:focus {
383
+ border-color: #667eea;
384
+ }
385
+
386
+ .send-btn {
387
+ width: 40px;
388
+ height: 40px;
389
+ border-radius: 50%;
390
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
391
+ border: none;
392
+ color: white;
393
+ cursor: pointer;
394
+ display: flex;
395
+ align-items: center;
396
+ justify-content: center;
397
+ transition: all 0.2s ease;
398
+ }
399
+
400
+ .send-btn:hover:not(:disabled) {
401
+ transform: scale(1.05);
402
+ }
403
+
404
+ .send-btn:disabled {
405
+ opacity: 0.5;
406
+ cursor: not-allowed;
407
+ }
408
+
409
+ .loading-indicator {
410
+ display: flex;
411
+ align-items: center;
412
+ gap: 8px;
413
+ color: #666;
414
+ font-size: 12px;
415
+ }
416
+
417
+ .loading-dots {
418
+ display: flex;
419
+ gap: 4px;
420
+ }
421
+
422
+ .loading-dot {
423
+ width: 6px;
424
+ height: 6px;
425
+ border-radius: 50%;
426
+ background: #667eea;
427
+ animation: loading 1.4s infinite ease-in-out;
428
+ }
429
+
430
+ .loading-dot:nth-child(1) {
431
+ animation-delay: -0.32s;
432
+ }
433
+ .loading-dot:nth-child(2) {
434
+ animation-delay: -0.16s;
435
+ }
436
+
437
+ @keyframes loading {
438
+ 0%,
439
+ 80%,
440
+ 100% {
441
+ transform: scale(0);
442
+ }
443
+ 40% {
444
+ transform: scale(1);
445
+ }
446
+ }
447
+
448
+ /* 스크롤바 스타일링 */
449
+ .messages-container::-webkit-scrollbar {
450
+ width: 6px;
451
+ }
452
+
453
+ .messages-container::-webkit-scrollbar-track {
454
+ background: #f1f1f1;
455
+ border-radius: 3px;
456
+ }
457
+
458
+ .messages-container::-webkit-scrollbar-thumb {
459
+ background: #c1c1c1;
460
+ border-radius: 3px;
461
+ }
462
+
463
+ .messages-container::-webkit-scrollbar-thumb:hover {
464
+ background: #a8a8a8;
465
+ }
466
+ `;
467
+ // 커스텀 엘리먼트 등록
468
+ customElements.define('chatbot-widget', ChatbotWidget);
469
+ //# sourceMappingURL=chatbot-widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatbot-widget.js","sourceRoot":"","sources":["../../../client/pages/lib/chatbot-widget.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css } from 'lit-element'\n\n// API 상수\nconst CHATBOT_API_URL = 'https://api.example.com/chatbot'\n\ninterface ChatMessage {\n id: string\n text: string\n isUser: boolean\n timestamp: Date\n}\n\nexport class ChatbotWidget extends LitElement {\n static get properties() {\n return {\n isOpen: { type: Boolean },\n messages: { type: Array },\n inputValue: { type: String },\n isLoading: { type: Boolean }\n }\n }\n\n isOpen = false\n messages: ChatMessage[] = []\n inputValue = ''\n isLoading = false\n\n private boundHandleEscapeKey = this.handleEscapeKey.bind(this)\n\n connectedCallback() {\n super.connectedCallback()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n // 컴포넌트가 제거될 때 이벤트 리스너 정리\n document.removeEventListener('keydown', this.boundHandleEscapeKey)\n }\n\n handleEscapeKey(event: KeyboardEvent) {\n if (event.key === 'Escape' && this.isOpen) {\n this.isOpen = false\n }\n }\n\n static styles = css`\n :host {\n position: fixed;\n bottom: 40px;\n right: 20px;\n z-index: 1000;\n }\n\n .chatbot-container {\n position: relative;\n }\n\n .chatbot-icon {\n width: 60px;\n height: 60px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n transition: all 0.3s ease;\n border: none;\n outline: none;\n }\n\n .chatbot-icon:hover {\n transform: scale(1.1);\n box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2);\n }\n\n .chatbot-icon svg {\n width: 28px;\n height: 28px;\n fill: white;\n }\n\n .chat-window {\n position: absolute;\n bottom: 80px;\n right: 0;\n width: 350px;\n height: 500px;\n background: white;\n border-radius: 20px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n transform: translateY(20px) scale(0.95);\n opacity: 0;\n transition: all 0.3s ease;\n border: 1px solid #e1e5e9;\n pointer-events: none; /* 닫혀있을 때 클릭 차단 해제 */\n }\n\n .chat-window.open {\n transform: translateY(0) scale(1);\n opacity: 1;\n pointer-events: auto; /* 열렸을 때 클릭 가능 */\n }\n\n .chat-header {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n padding: 15px 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .chat-header h3 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n }\n\n .close-btn {\n background: none;\n border: none;\n color: white;\n cursor: pointer;\n font-size: 32px;\n padding: 0;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n transition: background-color 0.2s ease;\n }\n\n .close-btn:hover {\n background-color: rgba(255, 255, 255, 0.2);\n }\n\n .messages-container {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n display: flex;\n flex-direction: column;\n gap: 15px;\n }\n\n .message {\n display: flex;\n align-items: flex-end;\n gap: 10px;\n max-width: 80%;\n }\n\n .message.user {\n align-self: flex-end;\n flex-direction: row-reverse;\n }\n\n .message.bot {\n align-self: flex-start;\n }\n\n .message-bubble {\n padding: 12px 16px;\n border-radius: 18px;\n font-size: 14px;\n line-height: 1.4;\n word-wrap: break-word;\n position: relative;\n }\n\n .message.user .message-bubble {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border-bottom-right-radius: 4px;\n }\n\n .message.bot .message-bubble {\n background: #f1f3f4;\n color: #333;\n border-bottom-left-radius: 4px;\n }\n\n .message-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n flex-shrink: 0;\n }\n\n .message.user .message-avatar {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n }\n\n .message.bot .message-avatar {\n background: #e8eaf0;\n color: #666;\n }\n\n .input-container {\n padding: 20px;\n border-top: 1px solid #e1e5e9;\n display: flex;\n gap: 10px;\n align-items: center;\n }\n\n .message-input {\n flex: 1;\n border: 1px solid #e1e5e9;\n border-radius: 25px;\n padding: 12px 16px;\n font-size: 14px;\n outline: none;\n transition: border-color 0.2s ease;\n }\n\n .message-input:focus {\n border-color: #667eea;\n }\n\n .send-btn {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n border: none;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n }\n\n .send-btn:hover:not(:disabled) {\n transform: scale(1.05);\n }\n\n .send-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .loading-indicator {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #666;\n font-size: 12px;\n }\n\n .loading-dots {\n display: flex;\n gap: 4px;\n }\n\n .loading-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #667eea;\n animation: loading 1.4s infinite ease-in-out;\n }\n\n .loading-dot:nth-child(1) {\n animation-delay: -0.32s;\n }\n .loading-dot:nth-child(2) {\n animation-delay: -0.16s;\n }\n\n @keyframes loading {\n 0%,\n 80%,\n 100% {\n transform: scale(0);\n }\n 40% {\n transform: scale(1);\n }\n }\n\n /* 스크롤바 스타일링 */\n .messages-container::-webkit-scrollbar {\n width: 6px;\n }\n\n .messages-container::-webkit-scrollbar-track {\n background: #f1f1f1;\n border-radius: 3px;\n }\n\n .messages-container::-webkit-scrollbar-thumb {\n background: #c1c1c1;\n border-radius: 3px;\n }\n\n .messages-container::-webkit-scrollbar-thumb:hover {\n background: #a8a8a8;\n }\n `\n\n toggleChat() {\n this.isOpen = !this.isOpen\n if (this.isOpen) {\n // 챗봇이 열릴 때 ESC 키 이벤트 리스너 추가\n document.addEventListener('keydown', this.boundHandleEscapeKey)\n // 챗봇이 열릴 때 스크롤을 맨 아래로\n this.requestUpdate()\n setTimeout(() => this.scrollToBottom(), 100)\n } else {\n // 챗봇이 닫힐 때 ESC 키 이벤트 리스너 제거\n document.removeEventListener('keydown', this.boundHandleEscapeKey)\n }\n }\n\n scrollToBottom() {\n const messagesContainer = this.shadowRoot?.querySelector('.messages-container') as HTMLElement\n if (messagesContainer) {\n messagesContainer.scrollTop = messagesContainer.scrollHeight\n }\n }\n\n async sendMessage() {\n if (!this.inputValue.trim() || this.isLoading) return\n\n const userMessage: ChatMessage = {\n id: Date.now().toString(),\n text: this.inputValue.trim(),\n isUser: true,\n timestamp: new Date()\n }\n\n this.messages = [...this.messages, userMessage]\n const messageText = this.inputValue.trim()\n this.inputValue = ''\n this.isLoading = true\n\n // 사용자 메시지 추가 후 스크롤을 맨 아래로\n setTimeout(() => this.scrollToBottom(), 50)\n\n // 포커스 유지를 위해 input 요소 참조 저장\n const input = this.shadowRoot?.querySelector('.message-input') as HTMLInputElement\n\n try {\n // API 호출\n const response = await fetch(CHATBOT_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({ message: messageText })\n })\n\n const data = await response.json()\n\n const botMessage: ChatMessage = {\n id: (Date.now() + 1).toString(),\n text: data.response || '죄송합니다. 응답을 처리할 수 없습니다.',\n isUser: false,\n timestamp: new Date()\n }\n\n this.messages = [...this.messages, botMessage]\n // 봇 메시지 추가 후 스크롤을 맨 아래로\n setTimeout(() => this.scrollToBottom(), 50)\n } catch (error) {\n console.error('챗봇 API 오류:', error)\n const errorMessage: ChatMessage = {\n id: (Date.now() + 1).toString(),\n text: '죄송합니다. 일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요.',\n isUser: false,\n timestamp: new Date()\n }\n this.messages = [...this.messages, errorMessage]\n // 에러 메시지 추가 후 스크롤을 맨 아래로\n setTimeout(() => this.scrollToBottom(), 50)\n } finally {\n this.isLoading = false\n // 로딩 완료 후 포커스 복원\n setTimeout(() => {\n if (input) {\n input.focus()\n }\n }, 100)\n }\n }\n\n handleKeyPress(event: KeyboardEvent) {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault()\n this.sendMessage()\n }\n }\n\n render() {\n return html`\n <div class=\"chatbot-container\">\n <!-- 챗봇 아이콘 -->\n <button class=\"chatbot-icon\" @click=\"${this.toggleChat}\">\n <svg viewBox=\"0 0 24 24\">\n <path\n d=\"M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z\"\n />\n </svg>\n </button>\n\n <!-- 대화창 -->\n <div class=\"chat-window ${this.isOpen ? 'open' : ''}\">\n <!-- 헤더 -->\n <div class=\"chat-header\">\n <h3>챗봇 도우미</h3>\n <button class=\"close-btn\" @click=\"${this.toggleChat}\">×</button>\n </div>\n\n <!-- 메시지 영역 -->\n <div class=\"messages-container\">\n ${this.messages.length === 0\n ? html`\n <div class=\"message bot\">\n <div class=\"message-avatar\">🤖</div>\n <div class=\"message-bubble\">안녕하세요! 무엇을 도와드릴까요?</div>\n </div>\n `\n : ''}\n ${this.messages.map(\n message => html`\n <div class=\"message ${message.isUser ? 'user' : 'bot'}\">\n <div class=\"message-avatar\">${message.isUser ? '👤' : '🤖'}</div>\n <div class=\"message-bubble\">${message.text}</div>\n </div>\n `\n )}\n ${this.isLoading\n ? html`\n <div class=\"message bot\">\n <div class=\"message-avatar\">🤖</div>\n <div class=\"message-bubble\">\n <div class=\"loading-indicator\">\n <span>답변 중</span>\n <div class=\"loading-dots\">\n <div class=\"loading-dot\"></div>\n <div class=\"loading-dot\"></div>\n <div class=\"loading-dot\"></div>\n </div>\n </div>\n </div>\n </div>\n `\n : ''}\n </div>\n\n <!-- 입력 영역 -->\n <div class=\"input-container\">\n <input\n class=\"message-input\"\n type=\"text\"\n placeholder=\"메시지를 입력하세요...\"\n .value=\"${this.inputValue}\"\n @input=\"${(e: Event) => (this.inputValue = (e.target as HTMLInputElement).value)}\"\n @keypress=\"${this.handleKeyPress}\"\n ?disabled=\"${this.isLoading}\"\n />\n <button class=\"send-btn\" @click=\"${this.sendMessage}\" ?disabled=\"${this.isLoading || !this.inputValue.trim()}\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n `\n }\n}\n\n// 커스텀 엘리먼트 등록\ncustomElements.define('chatbot-widget', ChatbotWidget)\n\n// 전역에서 사용할 수 있도록 선언\ndeclare global {\n interface HTMLElementTagNameMap {\n 'chatbot-widget': ChatbotWidget\n }\n}\n"]}
@@ -296,11 +296,11 @@ let ProjectDetail = class ProjectDetail extends ScopedElementsMixin(PageView) {
296
296
  </span>
297
297
  <span>
298
298
  <div>검측요청</div>
299
- <div bold>${this.inspectionSummary.wait}</div>
299
+ <div bold>${this.inspectionSummary.request}</div>
300
300
  </span>
301
301
  <span>
302
302
  <div>검측대기</div>
303
- <div bold>${this.inspectionSummary.request}</div>
303
+ <div bold>${this.inspectionSummary.wait}</div>
304
304
  </span>
305
305
  <span>
306
306
  <div>합격</div>