scipious commited on
Commit
348de14
·
verified ·
1 Parent(s): b72a629

Update templates/chat.html

Browse files
Files changed (1) hide show
  1. templates/chat.html +1242 -1099
templates/chat.html CHANGED
@@ -1,1100 +1,1243 @@
1
- <!DOCTYPE html>
2
- <html lang="ko">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>LexiMind - 자동차 인증 법규를 더 쉽게</title>
7
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
- <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
9
- <style>
10
- :root {
11
- --primary-color: #0066FF;
12
- --primary-hover: #4da8ff;
13
- --bg-primary: #1a1a1a;
14
- --bg-secondary: #2c2c2c;
15
- --bg-tertiary: #333;
16
- --bg-quaternary: #444;
17
- --text-primary: #e0e0e0;
18
- --text-secondary: #b0b0b0;
19
- --text-muted: #888;
20
- --border-color: #444;
21
- --shadow: 0 2px 8px rgba(0,0,0,0.2);
22
- --radius: 8px;
23
- --success-color: #4caf50;
24
- --warning-color: #ff9800;
25
- }
26
-
27
- * { box-sizing: border-box; }
28
-
29
- body {
30
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans KR', sans-serif;
31
- margin: 0; padding: 0;
32
- background-color: var(--bg-primary);
33
- color: var(--text-primary);
34
- line-height: 1.6;
35
- }
36
-
37
- /* ===== 헤더 ===== */
38
- .header {
39
- background: var(--bg-secondary);
40
- padding: 16px 24px;
41
- box-shadow: var(--shadow);
42
- display: flex; justify-content: space-between; align-items: center;
43
- position: sticky; top: 0; z-index: 100;
44
- }
45
-
46
- .logo {
47
- font-size: 28px; font-weight: 700; color: var(--primary-color); letter-spacing: -0.5px;
48
- }
49
-
50
- .nav {
51
- display: flex; list-style: none; gap: 32px; margin: 0; padding: 0;
52
- }
53
-
54
- .nav a {
55
- text-decoration: none; color: var(--text-secondary); font-weight: 500;
56
- padding: 8px 12px; border-radius: var(--radius); transition: all 0.2s ease;
57
- }
58
-
59
- .nav a:hover {
60
- color: var(--primary-hover); background: rgba(77, 168, 255, 0.1);
61
- }
62
-
63
- /* ===== 사이드바 컨트롤 ===== */
64
- .sidebar-toggle {
65
- position: fixed;
66
- top: 100px;
67
- left: 30px;
68
- width: 40px;
69
- height: 40px;
70
- background: rgba(0, 102, 255, 0.7);
71
- border: 1px solid rgba(255, 255, 255, 0.2);
72
- border-radius: 50%;
73
- cursor: pointer;
74
- z-index: 1001;
75
- transition: all 0.3s ease;
76
- display: flex;
77
- align-items: center;
78
- justify-content: center;
79
- backdrop-filter: blur(10px);
80
- box-shadow: 0 8px 32px rgba(0, 102, 255, 0.3);
81
- }
82
-
83
- .sidebar-toggle:hover {
84
- background: rgba(77, 168, 255, 0.8);
85
- transform: scale(1.05);
86
- }
87
-
88
- .hamburger {
89
- width: 15px;
90
- height: 2px;
91
- background: white;
92
- position: relative;
93
- transition: all 0.3s ease;
94
- transform: translateX(-7px);
95
- }
96
-
97
- .hamburger::before,
98
- .hamburger::after {
99
- content: '';
100
- position: absolute;
101
- width: 15px;
102
- height: 2px;
103
- background: white;
104
- transition: all 0.3s ease;
105
- }
106
-
107
- .hamburger::before { top: -6px; }
108
- .hamburger::after { top: 6px; }
109
-
110
- .sidebar-toggle.active .hamburger { background: transparent; }
111
- .sidebar-toggle.active .hamburger::before { transform: rotate(45deg); top: 0; }
112
- .sidebar-toggle.active .hamburger::after { transform: rotate(-45deg); top: 0; }
113
-
114
- .new-page-btn {
115
- position: fixed;
116
- top: 100px;
117
- left: 85px;
118
- min-width: 120px;
119
- height: 40px;
120
- padding: 8px 16px;
121
- background: rgba(255, 255, 255, 1);
122
- border: 2px solid rgba(0, 102, 255, 0.2);
123
- border-radius: 12px;
124
- cursor: pointer;
125
- z-index: 1001;
126
- transition: all 0.3s ease;
127
- display: flex;
128
- align-items: center;
129
- justify-content: center;
130
- gap: 8px;
131
- opacity: 0;
132
- visibility: hidden;
133
- transform: translateX(-20px);
134
- }
135
-
136
- .new-page-btn.show {
137
- opacity: 1;
138
- visibility: visible;
139
- transform: translateX(0);
140
- }
141
-
142
- .new-page-btn:hover {
143
- background: rgba(255, 255, 255, 0.8);
144
- transform: translateY(-2px);
145
- }
146
-
147
- .plus-icon {
148
- color: #0066FF;
149
- font-size: 16px;
150
- font-weight: 700;
151
- }
152
-
153
- /* ===== 사이드바 ===== */
154
- .sidebar {
155
- position: fixed; top: 76px; left: -300px; width: 300px;
156
- height: calc(100vh - 76px); background: #2c2c2c;
157
- transition: left 0.3s ease; z-index: 1000; overflow-y: auto;
158
- }
159
- .sidebar.open { left: 0; }
160
-
161
- .sidebar-content {
162
- padding: 75px 30px 30px 30px;
163
- }
164
-
165
- .sidebar-overlay {
166
- position: fixed; top: 76px; left: 0; width: 100%;
167
- height: calc(100vh - 76px); background: rgba(0, 0, 0, 0.5);
168
- opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 999;
169
- }
170
- .sidebar-overlay.active { opacity: 1; visibility: visible; }
171
-
172
- .date-ranges {
173
- display: flex;
174
- flex-direction: column;
175
- gap: 12px;
176
- }
177
-
178
- .date-range-item {
179
- display: block;
180
- padding: 16px;
181
- background: var(--bg-tertiary);
182
- border: 2px solid transparent;
183
- border-radius: var(--radius);
184
- text-decoration: none;
185
- color: var(--text-primary);
186
- transition: all 0.3s ease;
187
- }
188
-
189
- .date-range-item:hover {
190
- background: var(--bg-quaternary);
191
- border-color: var(--primary-color);
192
- transform: translateY(-2px);
193
- }
194
-
195
- .date-period {
196
- display: block;
197
- font-weight: 600;
198
- font-size: 14px;
199
- color: var(--primary-color);
200
- margin-bottom: 4px;
201
- }
202
-
203
- .date-count {
204
- display: block;
205
- font-size: 12px;
206
- color: var(--text-muted);
207
- }
208
-
209
- /* ===== 메인 레이아웃 ===== */
210
- .main {
211
- display: flex;
212
- height: calc(100vh - 76px);
213
- max-width: 100%;
214
- margin: 0;
215
- padding: 0;
216
- }
217
-
218
- /* ===== 필터 패널 ===== */
219
- .filter-panel {
220
- width: 350px;
221
- background: var(--bg-secondary);
222
- border-right: 1px solid var(--border-color);
223
- display: flex;
224
- flex-direction: column;
225
- overflow: hidden;
226
- }
227
-
228
- .filter-header {
229
- padding: 100px 20px 20px 20px;
230
- border-bottom: 1px solid var(--border-color);
231
- background: var(--bg-tertiary);
232
- }
233
- .filter-title {
234
- font-size: 20px;
235
- font-weight: 700;
236
- color: var(--text-primary);
237
- margin: 0 0 16px 0;
238
- }
239
-
240
- /* 지역 선택 토글 */
241
- .toggle-group { margin-bottom: 24px; }
242
- .toggle-label {
243
- display: block;
244
- font-size: 14px;
245
- font-weight: 600;
246
- color: var(--text-secondary);
247
- margin-bottom: 12px;
248
- }
249
- .toggle-options {
250
- display: flex;
251
- gap: 8px;
252
- flex-wrap: wrap;
253
- }
254
- .toggle-btn {
255
- padding: 8px 16px;
256
- background: var(--bg-quaternary);
257
- border: 2px solid transparent;
258
- border-radius: 20px;
259
- color: var(--text-secondary);
260
- cursor: pointer;
261
- transition: all 0.2s ease;
262
- font-size: 13px;
263
- font-weight: 500;
264
- }
265
- .toggle-btn:hover {
266
- background: #3a3a3a;
267
- color: var(--text-primary);
268
- }
269
- .toggle-btn.active {
270
- background: var(--primary-color);
271
- border-color: var(--primary-hover);
272
- color: white;
273
- }
274
-
275
- /* 법규 목록 스타일 */
276
- .regulation-list-container {
277
- border-bottom: 1px solid var(--border-color);
278
- padding: 24px;
279
- max-height: 250px;
280
- overflow-y: auto;
281
- }
282
-
283
- .regulation-header {
284
- display: flex;
285
- justify-content: space-between;
286
- align-items: center;
287
- margin-bottom: 12px;
288
- }
289
-
290
- .selected-count {
291
- font-size: 12px;
292
- color: var(--primary-color);
293
- background: rgba(77, 168, 255, 0.1);
294
- padding: 4px 8px;
295
- border-radius: 4px;
296
- }
297
-
298
- .regulation-item {
299
- padding: 12px;
300
- margin: 8px 0;
301
- background: var(--bg-quaternary);
302
- border: 2px solid transparent;
303
- border-radius: var(--radius);
304
- cursor: pointer;
305
- transition: all 0.3s ease;
306
- font-size: 14px;
307
- color: var(--text-primary);
308
- position: relative;
309
- display: flex;
310
- align-items: center;
311
- gap: 8px;
312
- }
313
-
314
- .regulation-item.selected {
315
- border-color: var(--primary-color);
316
- background: rgba(0, 102, 255, 0.1);
317
- }
318
-
319
- .regulation-item:hover {
320
- transform: translateY(-2px);
321
- background: #3a3a3a;
322
- }
323
-
324
- /* 상세 법규 리스트 스타일 */
325
- .regulation-details-container {
326
- padding: 24px;
327
- flex: 1;
328
- overflow-y: auto;
329
- }
330
-
331
- .regulation-details-header {
332
- display: flex;
333
- justify-content: space-between;
334
- align-items: center;
335
- margin-bottom: 20px;
336
- }
337
-
338
- .details-title {
339
- font-size: 16px;
340
- font-weight: 600;
341
- color: var(--text-primary);
342
- }
343
-
344
- .selected-details-count {
345
- font-size: 12px;
346
- color: var(--primary-color);
347
- background: rgba(77, 168, 255, 0.1);
348
- padding: 4px 8px;
349
- border-radius: 4px;
350
- }
351
-
352
- .regulation-detail-item {
353
- padding: 12px;
354
- margin: 8px 0;
355
- background: var(--bg-quaternary);
356
- border: 2px solid transparent;
357
- border-radius: var(--radius);
358
- cursor: pointer;
359
- transition: all 0.3s ease;
360
- font-size: 14px;
361
- color: var(--text-primary);
362
- display: flex;
363
- align-items: center;
364
- gap: 8px;
365
- position: relative;
366
- }
367
-
368
- .regulation-detail-item.selected {
369
- border-color: var(--primary-color);
370
- background: rgba(0, 102, 255, 0.1);
371
- }
372
-
373
- .regulation-detail-item:hover {
374
- transform: translateY(-2px);
375
- background: #3a3a3a;
376
- }
377
-
378
- .regulation-search-container {
379
- display: none;
380
- margin-bottom: 16px;
381
- }
382
-
383
- .regulation-search-input {
384
- width: 100%;
385
- padding: 12px;
386
- background: var(--bg-tertiary);
387
- border: 1px solid var(--border-color);
388
- border-radius: var(--radius);
389
- color: var(--text-primary);
390
- font-size: 14px;
391
- }
392
-
393
- .regulation-search-input::placeholder {
394
- color: var(--text-muted);
395
- }
396
-
397
- .regulation-search-input:focus {
398
- border-color: var(--primary-color);
399
- outline: none;
400
- }
401
-
402
- .regulation-detail-item.hidden {
403
- display: none;
404
- }
405
-
406
- .clear-selection {
407
- background: transparent;
408
- border: none;
409
- color: var(--text-muted);
410
- cursor: pointer;
411
- font-size: 12px;
412
- padding: 4px 8px;
413
- transition: color 0.2s ease;
414
- }
415
-
416
- .clear-selection:hover {
417
- color: #ff6b6b;
418
- }
419
-
420
- /* ===== 채팅 영역 ===== */
421
- .chat-area {
422
- flex: 1;
423
- display: flex;
424
- flex-direction: column;
425
- background: var(--bg-primary);
426
- padding: 0;
427
- }
428
-
429
- .chat-messages {
430
- flex: 1;
431
- overflow-y: auto;
432
- padding: 24px;
433
- display: flex;
434
- flex-direction: column;
435
- gap: 24px;
436
- }
437
-
438
- .message {
439
- display: flex;
440
- gap: 12px;
441
- max-width: 85%;
442
- }
443
-
444
- .message.user {
445
- margin-left: auto;
446
- flex-direction: row-reverse;
447
- }
448
-
449
- .message.assistant {
450
- margin-right: auto;
451
- }
452
-
453
- .message-avatar {
454
- width: 40px;
455
- height: 40px;
456
- border-radius: 50%;
457
- background: var(--primary-color);
458
- display: flex;
459
- align-items: center;
460
- justify-content: center;
461
- font-weight: 600;
462
- color: white;
463
- flex-shrink: 0;
464
- }
465
-
466
- .message.user .message-avatar {
467
- background: #4caf50;
468
- }
469
-
470
- .message-content {
471
- padding: 16px;
472
- border-radius: var(--radius);
473
- background: var(--bg-tertiary);
474
- font-size: 15px;
475
- line-height: 1.6;
476
- word-break: break-word;
477
- white-space: pre-wrap;
478
- }
479
-
480
- .message.user .message-content {
481
- background: var(--primary-color);
482
- color: white;
483
- }
484
-
485
- .message-time {
486
- font-size: 12px;
487
- color: var(--text-muted);
488
- margin-top: 4px;
489
- text-align: right;
490
- }
491
-
492
- .message.user .message-time {
493
- text-align: left;
494
- }
495
-
496
- .typing-indicator {
497
- display: flex;
498
- gap: 8px;
499
- padding: 16px;
500
- background: var(--bg-tertiary);
501
- border-radius: var(--radius);
502
- width: fit-content;
503
- }
504
-
505
- .typing-dot {
506
- width: 8px;
507
- height: 8px;
508
- background: var(--text-muted);
509
- border-radius: 50%;
510
- animation: typing 1.4s infinite ease-in-out;
511
- }
512
-
513
- .typing-dot:nth-child(2) { animation-delay: 0.2s; }
514
- .typing-dot:nth-child(3) { animation-delay: 0.4s; }
515
-
516
- @keyframes typing {
517
- 0%, 80%, 100% { transform: translateY(0); }
518
- 40% { transform: translateY(-4px); }
519
- }
520
-
521
- /* ===== 입력 영역 ===== */
522
- .input-area {
523
- padding: 24px;
524
- background: var(--bg-secondary);
525
- border-top: 1px solid var(--border-color);
526
- position: sticky;
527
- bottom: 0;
528
- }
529
-
530
- .input-wrapper {
531
- display: flex;
532
- gap: 12px;
533
- align-items: center;
534
- }
535
-
536
- #userInput {
537
- flex: 1;
538
- padding: 16px 20px;
539
- background: var(--bg-tertiary);
540
- border: 1px solid var(--border-color);
541
- border-radius: var(--radius);
542
- color: var(--text-primary);
543
- font-size: 15px;
544
- resize: none;
545
- line-height: 1.5;
546
- min-height: 48px;
547
- max-height: 200px;
548
- overflow-y: auto;
549
- }
550
-
551
- #userInput:focus {
552
- border-color: var(--primary-color);
553
- outline: none;
554
- }
555
-
556
- #userInput::placeholder {
557
- color: var(--text-muted);
558
- }
559
-
560
- #sendBtn {
561
- padding: 16px 32px;
562
- background: var(--primary-color);
563
- border: none;
564
- border-radius: var(--radius);
565
- color: white;
566
- font-weight: 600;
567
- cursor: pointer;
568
- transition: all 0.2s ease;
569
- font-size: 15px;
570
- }
571
-
572
- #sendBtn:hover {
573
- background: var(--primary-hover);
574
- transform: translateY(-2px);
575
- }
576
-
577
- #sendBtn:disabled {
578
- background: var(--text-muted);
579
- cursor: not-allowed;
580
- transform: none;
581
- }
582
-
583
- /* ===== 로딩 인디케이터 (추가) ===== */
584
- #loading {
585
- position: fixed;
586
- top: 0;
587
- left: 0;
588
- width: 100%;
589
- height: 100%;
590
- background: rgba(26, 26, 26, 0.9);
591
- display: flex;
592
- flex-direction: column;
593
- align-items: center;
594
- justify-content: center;
595
- z-index: 2000;
596
- transition: opacity 0.3s ease;
597
- }
598
-
599
- #loading.hidden {
600
- opacity: 0;
601
- visibility: hidden;
602
- }
603
-
604
- #loading p {
605
- font-size: 24px;
606
- font-weight: 600;
607
- color: var(--text-primary);
608
- margin-bottom: 24px;
609
- }
610
-
611
- #status {
612
- width: 60%;
613
- max-height: 40vh;
614
- overflow-y: auto;
615
- background: var(--bg-secondary);
616
- border-radius: var(--radius);
617
- padding: 20px;
618
- box-shadow: var(--shadow);
619
- }
620
-
621
- #status p {
622
- font-size: 16px;
623
- color: var(--text-secondary);
624
- margin: 8px 0;
625
- padding: 8px;
626
- background: var(--bg-tertiary);
627
- border-radius: 4px;
628
- }
629
-
630
- .loading-spinner {
631
- width: 50px;
632
- height: 50px;
633
- border: 5px solid var(--text-muted);
634
- border-top: 5px solid var(--primary-color);
635
- border-radius: 50%;
636
- animation: spin 1s linear infinite;
637
- margin-bottom: 24px;
638
- }
639
-
640
- @keyframes spin {
641
- 0% { transform: rotate(0deg); }
642
- 100% { transform: rotate(360deg); }
643
- }
644
- </style>
645
- </head>
646
- <body>
647
- <header class="header">
648
- <div class="logo">LexiMind</div>
649
- <ul class="nav">
650
- <li><a href="#">홈</a></li>
651
- <li><a href="#">설정</a></li>
652
- <li><a href="#">도움말</a></li>
653
- </ul>
654
- </header>
655
-
656
- <!-- 사이드바 토글 버튼 -->
657
- <div class="sidebar-toggle" onclick="toggleSidebar()">
658
- <div class="hamburger"></div>
659
- </div>
660
-
661
- <!-- 사이드바 -->
662
- <div class="sidebar" id="sidebar">
663
- <div class="sidebar-content">
664
- <h2 style="font-size: 20px; margin-bottom: 24px; color: var(--text-primary);">대화 히스토리</h2>
665
- <div class="date-ranges" id="dateRanges">
666
- <!-- 대화 히스토리 항목이 동적으로 추가됨 -->
667
- </div>
668
- </div>
669
- </div>
670
-
671
- <div class="sidebar-overlay" id="sidebarOverlay" onclick="toggleSidebar()"></div>
672
-
673
- <!-- 새 페이지 버튼 -->
674
- <button class="new-page-btn" onclick="createNewPage()">
675
- <span class="plus-icon">+</span> 새 페이지
676
- </button>
677
-
678
- <div class="main">
679
- <!-- 필터 패널 -->
680
- <div class="filter-panel">
681
- <div class="filter-header">
682
- <h2 class="filter-title">지역 선택</h2>
683
- <div class="toggle-group">
684
- <label class="toggle-label">검색 지역 선택</label>
685
- <div class="toggle-options" id="regionToggles">
686
- <!-- 토글 버튼 동적 생성 -->
687
- </div>
688
- </div>
689
- </div>
690
-
691
- <!-- 법규 리스트 -->
692
- <div class="regulation-list-container">
693
- <div class="regulation-header">
694
- <h3 style="font-size: 16px; font-weight: 600; color: var(--text-primary); margin: 0;">법규 리스트</h3>
695
- <span class="selected-count" id="selectedCount">선택: 0</span>
696
- </div>
697
- <div class="regulation-list" id="regulationList">
698
- <!-- 법규 항목 동적 추가 -->
699
- </div>
700
- <button class="clear-selection" onclick="clearAllSelections()">선택 해제</button>
701
- </div>
702
-
703
- <!-- 상세 법규 리스트 -->
704
- <div class="regulation-details-container">
705
- <div class="regulation-details-header">
706
- <h3 class="details-title">상세 법규 리스트</h3>
707
- <span class="selected-details-count" id="selectedDetailsCount">선택: 0</span>
708
- </div>
709
- <div class="regulation-search-container" id="regulationSearchContainer">
710
- <input type="text" class="regulation-search-input" id="regulationSearchInput" placeholder="법규 검색...">
711
- </div>
712
- <div id="regulationDetailsList">
713
- <!-- 상세 법규 항목 동적 추가 -->
714
- </div>
715
- </div>
716
- </div>
717
-
718
- <!-- 채팅 영역 -->
719
- <div class="chat-area">
720
- <div class="chat-messages" id="chatMessages">
721
- <!-- 메시지 동적 추가 -->
722
- </div>
723
- <div class="input-area">
724
- <div class="input-wrapper">
725
- <textarea id="userInput" placeholder="질문을 입력하세요..." rows="1"></textarea>
726
- <button id="sendBtn">전송</button>
727
- </div>
728
- </div>
729
- </div>
730
- </div>
731
-
732
- <!-- 로딩 인디케이터 (추가) -->
733
- <div id="loading">
734
- <div class="loading-spinner"></div>
735
- <p>데이터 로딩 중...</p>
736
- <div id="status"></div>
737
- </div>
738
-
739
- <script>
740
- const socket = io();
741
-
742
- // 초기 로딩 상태
743
- const loadingDiv = document.getElementById('loading');
744
- const statusDiv = document.getElementById('status');
745
- loadingDiv.classList.remove('hidden'); // 로딩 표시
746
-
747
- // SocketIO 메시지 수신 (로딩 상태 업데이트)
748
- socket.on('message', (data) => {
749
- const msgP = document.createElement('p');
750
- msgP.textContent = data.message;
751
- statusDiv.appendChild(msgP);
752
- statusDiv.scrollTop = statusDiv.scrollHeight;
753
-
754
- if (data.message === 'Ready to Search') {
755
- loadingDiv.classList.add('hidden'); // 로딩 완료 시 숨김
756
- }
757
- });
758
-
759
- // ===== 초기화 =====
760
- let selectedRegulations = [];
761
- let selectedRegions = [];
762
-
763
- document.addEventListener('DOMContentLoaded', () => {
764
- initializeRegionToggles();
765
- loadRegulationList();
766
- loadDetailedRegulationList();
767
-
768
- const userInput = document.getElementById('userInput');
769
- const sendBtn = document.getElementById('sendBtn');
770
-
771
- userInput.addEventListener('input', () => {
772
- sendBtn.disabled = !userInput.value.trim();
773
- });
774
-
775
- userInput.addEventListener('keypress', (e) => {
776
- if (e.key === 'Enter' && !e.shiftKey) {
777
- e.preventDefault();
778
- sendMessage();
779
- }
780
- });
781
-
782
- sendBtn.addEventListener('click', sendMessage);
783
- });
784
-
785
- // ===== 사이드바 토글 =====
786
- function toggleSidebar() {
787
- const sidebar = document.getElementById('sidebar');
788
- const overlay = document.getElementById('sidebarOverlay');
789
- const toggle = document.querySelector('.sidebar-toggle');
790
-
791
- sidebar.classList.toggle('open');
792
- overlay.classList.toggle('active');
793
- toggle.classList.toggle('active');
794
- }
795
-
796
- // ===== 새 페이지 생성 =====
797
- function createNewPage() {
798
- // 새 페이지 생성 로직 (예: 새 채팅 시작)
799
- console.log('새 페이지 생성');
800
- }
801
-
802
- // ===== 지역 토글 초기화 =====
803
- function initializeRegionToggles() {
804
- const regions = ['국내', '북미', '유럽'];
805
- const togglesDiv = document.getElementById('regionToggles');
806
-
807
- regions.forEach(region => {
808
- const btn = document.createElement('button');
809
- btn.className = 'toggle-btn';
810
- btn.textContent = region;
811
- btn.dataset.region = region;
812
- btn.onclick = () => toggleRegion(btn);
813
- togglesDiv.appendChild(btn);
814
- });
815
- }
816
-
817
- function toggleRegion(btn) {
818
- btn.classList.toggle('active');
819
- selectedRegions = Array.from(document.querySelectorAll('.toggle-btn.active'))
820
- .map(b => b.dataset.region);
821
- loadDetailedRegulationList();
822
- }
823
-
824
- function getSelectedRegions() {
825
- return selectedRegions.length > 0 ? selectedRegions : ['국내', '북미', '유럽'];
826
- }
827
-
828
- // ===== 법규 리스트 로드 =====
829
- function loadRegulationList() {
830
- // 법규 리스트 로드 로직 (예시 데이터)
831
- const regulationListDiv = document.getElementById('regulationList');
832
- const regulations = ['안전기준 34조', 'FMVSS 118', 'UN Regulation 21']; // 예시
833
-
834
- regulations.forEach((reg, index) => {
835
- const item = document.createElement('div');
836
- item.className = 'regulation-item';
837
- item.dataset.id = `reg_${index}`;
838
- item.textContent = reg;
839
- regulationListDiv.appendChild(item);
840
-
841
- item.addEventListener('click', () => toggleRegulationSelection(item));
842
- });
843
- }
844
-
845
- function toggleRegulationSelection(item) {
846
- item.classList.toggle('selected');
847
- const id = item.dataset.id;
848
-
849
- if (item.classList.contains('selected')) {
850
- selectedRegulations.push(id);
851
- } else {
852
- selectedRegulations = selectedRegulations.filter(regId => regId !== id);
853
- }
854
-
855
- updateSelectedCount();
856
- }
857
-
858
- function updateSelectedCount() {
859
- const countSpan = document.getElementById('selectedCount');
860
- countSpan.textContent = `선택: ${selectedRegulations.length}`;
861
- }
862
-
863
- // ===== 메시지 전송 =====
864
- function sendMessage() {
865
- const userInput = document.getElementById('userInput');
866
- const query = userInput.value.trim();
867
- if (!query) return;
868
-
869
- addMessage(query, 'user');
870
- userInput.value = '';
871
- sendBtn.disabled = true;
872
-
873
- showTypingIndicator();
874
-
875
- const requestData = {
876
- query: query,
877
- regions: getSelectedRegions(),
878
- selectedRegulations: selectedRegulations.map(id => {
879
- const element = document.querySelector(`[data-id="${id}"]`);
880
- return {
881
- id: id,
882
- title: element ? element.getAttribute('data-title') || element.textContent.trim() : id
883
- };
884
- })
885
- };
886
-
887
- fetch('/get_message', {
888
- method: 'POST',
889
- headers: {
890
- 'Content-Type': 'application/json'
891
- },
892
- body: JSON.stringify(requestData)
893
- })
894
- .then(response => response.json())
895
- .then(data => {
896
- hideTypingIndicator();
897
- if (data.message) {
898
- addMessage(data.message, 'assistant');
899
- }
900
- })
901
- .catch(error => {
902
- console.error('검색 오류:', error);
903
- hideTypingIndicator();
904
- addMessage('죄송합니다. 검색 중 오류가 발생했습니다.', 'assistant');
905
- });
906
- }
907
-
908
- function addMessage(text, type) {
909
- const chatMessages = document.getElementById('chatMessages');
910
- const messageDiv = document.createElement('div');
911
- messageDiv.className = `message ${type}`;
912
-
913
- const avatar = document.createElement('div');
914
- avatar.className = 'message-avatar';
915
- avatar.textContent = type === 'user' ? 'Me' : 'Lexi';
916
-
917
- const contentWrapper = document.createElement('div');
918
- const content = document.createElement('div');
919
- content.className = 'message-content';
920
-
921
- if (type === 'assistant') {
922
- const decodedText = decodeHtmlEntities(text);
923
- content.innerHTML = decodedText;
924
- } else {
925
- content.textContent = text;
926
- }
927
-
928
- const time = document.createElement('div');
929
- time.className = 'message-time';
930
- time.textContent = formatTime(new Date());
931
-
932
- contentWrapper.appendChild(content);
933
- contentWrapper.appendChild(time);
934
- messageDiv.appendChild(avatar);
935
- messageDiv.appendChild(contentWrapper);
936
- chatMessages.appendChild(messageDiv);
937
-
938
- chatMessages.scrollTop = chatMessages.scrollHeight;
939
- }
940
-
941
- function decodeHtmlEntities(text) {
942
- const textarea = document.createElement('textarea');
943
- textarea.innerHTML = text;
944
- return textarea.value;
945
- }
946
-
947
- function formatTime(date) {
948
- return date.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' });
949
- }
950
-
951
- function showTypingIndicator() {
952
- const chatMessages = document.getElementById('chatMessages');
953
- const indicator = document.createElement('div');
954
- indicator.className = 'message assistant';
955
- indicator.id = 'typingIndicator';
956
-
957
- const avatar = document.createElement('div');
958
- avatar.className = 'message-avatar';
959
- avatar.textContent = 'Lexi';
960
-
961
- const typing = document.createElement('div');
962
- typing.className = 'typing-indicator';
963
- typing.innerHTML = '<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>';
964
-
965
- indicator.appendChild(avatar);
966
- indicator.appendChild(typing);
967
- chatMessages.appendChild(indicator);
968
- chatMessages.scrollTop = chatMessages.scrollHeight;
969
- }
970
-
971
- function hideTypingIndicator() {
972
- const indicator = document.getElementById('typingIndicator');
973
- if (indicator) {
974
- indicator.remove();
975
- }
976
- }
977
-
978
- // ===== 상세 법규 리스트 관련 함수 =====
979
- function loadDetailedRegulationList() {
980
- const selectedRegions = getSelectedRegions();
981
- const detailsListDiv = document.getElementById('regulationDetailsList');
982
- const searchContainer = document.getElementById('regulationSearchContainer');
983
-
984
- detailsListDiv.innerHTML = '<p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">상세 법규 리스트 로딩 중...</p>';
985
-
986
- fetch('/get_reg_list', {
987
- method: 'POST',
988
- headers: {
989
- 'Content-Type': 'application/json'
990
- },
991
- body: JSON.stringify({ regions: selectedRegions })
992
- })
993
- .then(response => response.json())
994
- .then(data => {
995
- if (data.reg_list_part) {
996
- displayDetailedRegulationList(data.reg_list_part);
997
- searchContainer.style.display = 'block';
998
- initializeRegulationSearch();
999
- } else {
1000
- detailsListDiv.innerHTML = '<p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">상세 법규 리스트를 불러올 수 없습니다.</p>';
1001
- }
1002
- })
1003
- .catch(error => {
1004
- console.error('상세 법규 리스트 로딩 오류:', error);
1005
- detailsListDiv.innerHTML = '<p style="color: #ff6b6b; padding: 20px; text-align: center; margin: 0;">리스트 로딩 중 오류가 발생했습니다.</p>';
1006
- });
1007
- }
1008
-
1009
- function displayDetailedRegulationList(data) {
1010
- const detailsListDiv = document.getElementById('regulationDetailsList');
1011
-
1012
- let listHTML = '';
1013
-
1014
- if (typeof data === 'string') {
1015
- const lines = data.split('\n').filter(line => line.trim());
1016
- lines.forEach((line, index) => {
1017
- const regId = `detail_reg_${Date.now()}_${index}`;
1018
- const isSelected = selectedRegulations.includes(regId);
1019
- listHTML += `
1020
- <div class="regulation-detail-item ${isSelected ? 'selected' : ''}"
1021
- data-id="${regId}"
1022
- data-title="${line.trim()}"
1023
- data-search-text="${line.trim().toLowerCase()}">
1024
- ${line.trim()}
1025
- </div>
1026
- `;
1027
- });
1028
- } else if (Array.isArray(data)) {
1029
- data.forEach((item, index) => {
1030
- const regId = item.id || `detail_reg_${Date.now()}_${index}`;
1031
- const title = item.title || item.name || item.toString();
1032
- const isSelected = selectedRegulations.includes(regId);
1033
- listHTML += `
1034
- <div class="regulation-detail-item ${isSelected ? 'selected' : ''}"
1035
- data-id="${regId}"
1036
- data-title="${title}"
1037
- data-search-text="${title.toLowerCase()}">
1038
- ${title}
1039
- </div>
1040
- `;
1041
- });
1042
- } else {
1043
- listHTML = '<p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">상세 법규 데이터를 표시할 수 없습니다.</p>';
1044
- }
1045
-
1046
- detailsListDiv.innerHTML = listHTML;
1047
- updateSelectedCount();
1048
-
1049
- // 상세 법규 리스트 클릭 이벤트 추가
1050
- detailsListDiv.addEventListener('click', function(e) {
1051
- const item = e.target.closest('.regulation-detail-item');
1052
- if (item) {
1053
- toggleRegulationSelection(item);
1054
- }
1055
- });
1056
- }
1057
-
1058
- function initializeRegulationSearch() {
1059
- const searchInput = document.getElementById('regulationSearchInput');
1060
-
1061
- searchInput.addEventListener('input', function() {
1062
- const searchTerm = this.value.toLowerCase().trim();
1063
- filterRegulationList(searchTerm);
1064
- });
1065
- }
1066
-
1067
- function filterRegulationList(searchTerm) {
1068
- const detailItems = document.querySelectorAll('.regulation-detail-item');
1069
-
1070
- detailItems.forEach(item => {
1071
- const searchText = item.getAttribute('data-search-text') || '';
1072
-
1073
- if (searchTerm === '' || searchText.includes(searchTerm)) {
1074
- item.classList.remove('hidden');
1075
- } else {
1076
- item.classList.add('hidden');
1077
- }
1078
- });
1079
- }
1080
-
1081
- function clearAllSelections() {
1082
- selectedRegulations = [];
1083
- document.querySelectorAll('.regulation-item').forEach(item => {
1084
- item.classList.remove('selected');
1085
- });
1086
- document.querySelectorAll('.regulation-detail-item').forEach(item => {
1087
- item.classList.remove('selected');
1088
- });
1089
- updateSelectedCount();
1090
-
1091
- // 검색창 초기화
1092
- const searchInput = document.getElementById('regulationSearchInput');
1093
- if (searchInput) {
1094
- searchInput.value = '';
1095
- filterRegulationList('');
1096
- }
1097
- }
1098
- </script>
1099
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1100
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LexiMind - 자동차 인증 법규를 더 쉽게</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
9
+ <style>
10
+ :root {
11
+ --primary-color: #0066FF;
12
+ --primary-hover: #4da8ff;
13
+ --bg-primary: #1a1a1a;
14
+ --bg-secondary: #2c2c2c;
15
+ --bg-tertiary: #333;
16
+ --bg-quaternary: #444;
17
+ --text-primary: #e0e0e0;
18
+ --text-secondary: #b0b0b0;
19
+ --text-muted: #888;
20
+ --border-color: #444;
21
+ --shadow: 0 2px 8px rgba(0,0,0,0.2);
22
+ --radius: 8px;
23
+ --success-color: #4caf50;
24
+ --warning-color: #ff9800;
25
+ }
26
+
27
+ * { box-sizing: border-box; }
28
+
29
+ body {
30
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans KR', sans-serif;
31
+ margin: 0; padding: 0;
32
+ background-color: var(--bg-primary);
33
+ color: var(--text-primary);
34
+ line-height: 1.6;
35
+ }
36
+
37
+ /* ===== 헤더 ===== */
38
+ .header {
39
+ background: var(--bg-secondary);
40
+ padding: 16px 24px;
41
+ box-shadow: var(--shadow);
42
+ display: flex; justify-content: space-between; align-items: center;
43
+ position: sticky; top: 0; z-index: 100;
44
+ }
45
+
46
+ .logo {
47
+ font-size: 28px; font-weight: 700; color: var(--primary-color); letter-spacing: -0.5px;
48
+ }
49
+
50
+ .nav {
51
+ display: flex; list-style: none; gap: 32px; margin: 0; padding: 0;
52
+ }
53
+
54
+ .nav a {
55
+ text-decoration: none; color: var(--text-secondary); font-weight: 500;
56
+ padding: 8px 12px; border-radius: var(--radius); transition: all 0.2s ease;
57
+ }
58
+
59
+ .nav a:hover {
60
+ color: var(--primary-hover); background: rgba(77, 168, 255, 0.1);
61
+ }
62
+
63
+ /* ===== 사이드바 컨트롤 ===== */
64
+ .sidebar-toggle {
65
+ position: fixed;
66
+ top: 100px;
67
+ left: 30px;
68
+ width: 40px;
69
+ height: 40px;
70
+ background: rgba(0, 102, 255, 0.7);
71
+ border: 1px solid rgba(255, 255, 255, 0.2);
72
+ border-radius: 50%;
73
+ cursor: pointer;
74
+ z-index: 1001;
75
+ transition: all 0.3s ease;
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ backdrop-filter: blur(10px);
80
+ box-shadow: 0 8px 32px rgba(0, 102, 255, 0.3);
81
+ }
82
+
83
+ .sidebar-toggle:hover {
84
+ background: rgba(77, 168, 255, 0.8);
85
+ transform: scale(1.05);
86
+ }
87
+
88
+ .hamburger {
89
+ width: 15px;
90
+ height: 2px;
91
+ background: white;
92
+ position: relative;
93
+ transition: all 0.3s ease;
94
+ transform: translateX(-7px);
95
+ }
96
+
97
+ .hamburger::before,
98
+ .hamburger::after {
99
+ content: '';
100
+ position: absolute;
101
+ width: 15px;
102
+ height: 2px;
103
+ background: white;
104
+ transition: all 0.3s ease;
105
+ }
106
+
107
+ .hamburger::before { top: -6px; }
108
+ .hamburger::after { top: 6px; }
109
+
110
+ .sidebar-toggle.active .hamburger { background: transparent; }
111
+ .sidebar-toggle.active .hamburger::before { transform: rotate(45deg); top: 0; }
112
+ .sidebar-toggle.active .hamburger::after { transform: rotate(-45deg); top: 0; }
113
+
114
+ .new-page-btn {
115
+ position: fixed;
116
+ top: 100px;
117
+ left: 85px;
118
+ min-width: 120px;
119
+ height: 40px;
120
+ padding: 8px 16px;
121
+ background: rgba(255, 255, 255, 1);
122
+ border: 2px solid rgba(0, 102, 255, 0.2);
123
+ border-radius: 12px;
124
+ cursor: pointer;
125
+ z-index: 1001;
126
+ transition: all 0.3s ease;
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ gap: 8px;
131
+ opacity: 0;
132
+ visibility: hidden;
133
+ transform: translateX(-20px);
134
+ }
135
+
136
+ .new-page-btn.show {
137
+ opacity: 1;
138
+ visibility: visible;
139
+ transform: translateX(0);
140
+ }
141
+
142
+ .new-page-btn:hover {
143
+ background: rgba(255, 255, 255, 0.8);
144
+ transform: translateY(-2px);
145
+ }
146
+
147
+ .plus-icon {
148
+ color: #0066FF;
149
+ font-size: 16px;
150
+ font-weight: 700;
151
+ }
152
+
153
+ /* ===== 사이드바 ===== */
154
+ .sidebar {
155
+ position: fixed; top: 76px; left: -300px; width: 300px;
156
+ height: calc(100vh - 76px); background: #2c2c2c;
157
+ transition: left 0.3s ease; z-index: 1000; overflow-y: auto;
158
+ }
159
+ .sidebar.open { left: 0; }
160
+
161
+ .sidebar-content {
162
+ padding: 75px 30px 30px 30px;
163
+ }
164
+
165
+ .sidebar-overlay {
166
+ position: fixed; top: 76px; left: 0; width: 100%;
167
+ height: calc(100vh - 76px); background: rgba(0, 0, 0, 0.5);
168
+ opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 999;
169
+ }
170
+ .sidebar-overlay.active { opacity: 1; visibility: visible; }
171
+
172
+ .date-ranges {
173
+ display: flex;
174
+ flex-direction: column;
175
+ gap: 12px;
176
+ }
177
+
178
+ .date-range-item {
179
+ display: block;
180
+ padding: 16px;
181
+ background: var(--bg-tertiary);
182
+ border: 2px solid transparent;
183
+ border-radius: var(--radius);
184
+ text-decoration: none;
185
+ color: var(--text-primary);
186
+ transition: all 0.3s ease;
187
+ }
188
+
189
+ .date-range-item:hover {
190
+ background: var(--bg-quaternary);
191
+ border-color: var(--primary-color);
192
+ transform: translateY(-2px);
193
+ }
194
+
195
+ .date-period {
196
+ display: block;
197
+ font-weight: 600;
198
+ font-size: 14px;
199
+ color: var(--primary-color);
200
+ margin-bottom: 4px;
201
+ }
202
+
203
+ .date-count {
204
+ display: block;
205
+ font-size: 12px;
206
+ color: var(--text-muted);
207
+ }
208
+
209
+ /* ===== 메인 레이아웃 ===== */
210
+ .main {
211
+ display: flex;
212
+ height: calc(100vh - 76px);
213
+ max-width: 100%;
214
+ margin: 0;
215
+ padding: 0;
216
+ }
217
+
218
+ /* ===== 필터 패널 ===== */
219
+ .filter-panel {
220
+ width: 350px;
221
+ background: var(--bg-secondary);
222
+ border-right: 1px solid var(--border-color);
223
+ display: flex;
224
+ flex-direction: column;
225
+ overflow: hidden;
226
+ }
227
+
228
+ .filter-header {
229
+ padding: 100px 20px 20px 20px;
230
+ border-bottom: 1px solid var(--border-color);
231
+ background: var(--bg-tertiary);
232
+ }
233
+ .filter-title {
234
+ font-size: 20px;
235
+ font-weight: 700;
236
+ color: var(--text-primary);
237
+ margin: 0 0 16px 0;
238
+ }
239
+
240
+ /* 지역 선택 토글 */
241
+ .toggle-group { margin-bottom: 24px; }
242
+ .toggle-label {
243
+ display: block;
244
+ font-size: 14px;
245
+ font-weight: 600;
246
+ color: var(--text-secondary);
247
+ margin-bottom: 12px;
248
+ }
249
+ .toggle-options {
250
+ display: flex;
251
+ gap: 8px;
252
+ flex-wrap: wrap;
253
+ }
254
+ .toggle-btn {
255
+ padding: 8px 16px;
256
+ background: var(--bg-quaternary);
257
+ border: 2px solid transparent;
258
+ border-radius: 20px;
259
+ color: var(--text-secondary);
260
+ cursor: pointer;
261
+ transition: all 0.2s ease;
262
+ font-size: 13px;
263
+ font-weight: 500;
264
+ }
265
+ .toggle-btn:hover {
266
+ background: #3a3a3a;
267
+ color: var(--text-primary);
268
+ }
269
+ .toggle-btn.active {
270
+ background: var(--primary-color);
271
+ border-color: var(--primary-hover);
272
+ color: white;
273
+ }
274
+
275
+ /* 법규 목록 스타일 */
276
+ .regulation-list-container {
277
+ border-bottom: 1px solid var(--border-color);
278
+ padding: 24px;
279
+ max-height: 250px;
280
+ overflow-y: auto;
281
+ }
282
+
283
+ .regulation-header {
284
+ display: flex;
285
+ justify-content: space-between;
286
+ align-items: center;
287
+ margin-bottom: 12px;
288
+ }
289
+
290
+ .selected-count {
291
+ font-size: 12px;
292
+ color: var(--primary-color);
293
+ background: rgba(77, 168, 255, 0.1);
294
+ padding: 4px 8px;
295
+ border-radius: 4px;
296
+ }
297
+
298
+ .regulation-item {
299
+ padding: 12px;
300
+ margin: 8px 0;
301
+ background: var(--bg-quaternary);
302
+ border-radius: var(--radius);
303
+ cursor: pointer;
304
+ transition: all 0.2s ease;
305
+ border: 2px solid transparent;
306
+ user-select: none;
307
+ }
308
+ .regulation-item:hover {
309
+ background: #3a3a3a;
310
+ border-color: var(--primary-color);
311
+ }
312
+ .regulation-item.selected {
313
+ background: var(--primary-color);
314
+ color: white;
315
+ border-color: var(--primary-hover);
316
+ }
317
+
318
+ /* ===== 상세 법규 리스트 ===== */
319
+ .regulation-details-container {
320
+ flex: 1;
321
+ display: flex;
322
+ flex-direction: column;
323
+ overflow: hidden;
324
+ }
325
+ .regulation-details-header {
326
+ display: flex;
327
+ justify-content: space-between;
328
+ align-items: center;
329
+ padding: 16px 24px;
330
+ border-bottom: 1px solid var(--border-color);
331
+ }
332
+
333
+ .btn {
334
+ padding: 8px 16px;
335
+ border: none;
336
+ border-radius: var(--radius);
337
+ background: var(--primary-color);
338
+ color: white;
339
+ cursor: pointer;
340
+ font-size: 13px;
341
+ font-weight: 600;
342
+ transition: all 0.2s ease;
343
+ }
344
+
345
+ .btn:hover {
346
+ background: var(--primary-hover);
347
+ transform: translateY(-1px);
348
+ }
349
+
350
+ .btn-small {
351
+ padding: 6px 12px;
352
+ font-size: 12px;
353
+ }
354
+
355
+ .btn-load {
356
+ background: var(--success-color);
357
+ }
358
+
359
+ .btn-load:hover {
360
+ background: #45a049;
361
+ }
362
+
363
+ .btn-secondary {
364
+ background: var(--bg-quaternary);
365
+ }
366
+
367
+ .btn-secondary:hover {
368
+ background: #555;
369
+ }
370
+
371
+ .regulation-search {
372
+ padding: 12px 24px;
373
+ background: var(--bg-tertiary);
374
+ border-bottom: 1px solid var(--border-color);
375
+ }
376
+ .regulation-search-input {
377
+ width: 100%;
378
+ padding: 8px 12px;
379
+ border: 1px solid var(--border-color);
380
+ border-radius: var(--radius);
381
+ background: var(--bg-quaternary);
382
+ color: var(--text-primary);
383
+ font-size: 13px;
384
+ transition: border-color 0.2s ease;
385
+ }
386
+ .regulation-search-input:focus {
387
+ outline: none;
388
+ border-color: var(--primary-color);
389
+ }
390
+ .regulation-search-input::placeholder {
391
+ color: var(--text-muted);
392
+ }
393
+ .regulation-details-list {
394
+ flex: 1;
395
+ overflow-y: auto;
396
+ padding: 12px 24px;
397
+ }
398
+ .regulation-detail-item {
399
+ padding: 10px 12px;
400
+ margin: 6px 0;
401
+ background: var(--bg-quaternary);
402
+ border-radius: var(--radius);
403
+ cursor: pointer;
404
+ transition: all 0.2s ease;
405
+ border: 2px solid transparent;
406
+ user-select: none;
407
+ font-size: 13px;
408
+ line-height: 1.4;
409
+ }
410
+ .regulation-detail-item:hover {
411
+ background: #3a3a3a;
412
+ border-color: var(--primary-color);
413
+ }
414
+ .regulation-detail-item.selected {
415
+ background: var(--primary-color);
416
+ color: white;
417
+ border-color: var(--primary-hover);
418
+ }
419
+ .regulation-detail-item.hidden {
420
+ display: none;
421
+ }
422
+
423
+ .action-buttons {
424
+ display: flex;
425
+ gap: 12px;
426
+ padding: 16px 24px;
427
+ border-top: 1px solid var(--border-color);
428
+ background: var(--bg-secondary);
429
+ }
430
+
431
+ /* ===== 채팅 패널 ===== */
432
+ .chat-panel {
433
+ flex: 1;
434
+ display: flex;
435
+ flex-direction: column;
436
+ background: var(--bg-primary);
437
+ }
438
+ .chat-header {
439
+ padding: 20px 24px;
440
+ background: var(--bg-secondary);
441
+ border-bottom: 1px solid var(--border-color);
442
+ }
443
+ .chat-title {
444
+ font-size: 18px;
445
+ font-weight: 600;
446
+ color: var(--text-primary);
447
+ margin: 0;
448
+ display: flex;
449
+ align-items: baseline;
450
+ gap: 10px;
451
+ }
452
+ .title-subtitle {
453
+ font-size: 14px;
454
+ font-weight: 400;
455
+ color: var(--text-muted);
456
+ }
457
+
458
+ .status-text {
459
+ margin-top: 8px;
460
+ padding: 8px 12px;
461
+ background: var(--bg-tertiary);
462
+ border-radius: var(--radius);
463
+ font-size: 12px;
464
+ color: var(--primary-color);
465
+ }
466
+
467
+ .chat-messages {
468
+ flex: 1;
469
+ overflow-y: auto;
470
+ padding: 24px;
471
+ display: flex;
472
+ flex-direction: column;
473
+ gap: 16px;
474
+ }
475
+
476
+ /* 메시지 버블 */
477
+ .message {
478
+ display: flex;
479
+ gap: 12px;
480
+ max-width: 80%;
481
+ animation: slideIn 0.3s ease;
482
+ }
483
+ @keyframes slideIn {
484
+ from { opacity: 0; transform: translateY(10px); }
485
+ to { opacity: 1; transform: translateY(0); }
486
+ }
487
+ .message.user {
488
+ align-self: flex-end;
489
+ flex-direction: row-reverse;
490
+ }
491
+ .message.assistant {
492
+ align-self: flex-start;
493
+ }
494
+ .message-avatar {
495
+ width: 36px;
496
+ height: 36px;
497
+ border-radius: 50%;
498
+ background: var(--primary-color);
499
+ display: flex;
500
+ align-items: center;
501
+ justify-content: center;
502
+ color: white;
503
+ font-weight: 700;
504
+ font-size: 14px;
505
+ flex-shrink: 0;
506
+ }
507
+ .message.user .message-avatar {
508
+ background: var(--success-color);
509
+ }
510
+ .message-content {
511
+ background: var(--bg-secondary);
512
+ padding: 12px 16px;
513
+ border-radius: 16px;
514
+ color: var(--text-primary);
515
+ line-height: 1.5;
516
+ word-wrap: break-word;
517
+ }
518
+ .message.user .message-content {
519
+ background: var(--primary-color);
520
+ color: white;
521
+ }
522
+ .message-time {
523
+ font-size: 11px;
524
+ color: var(--text-muted);
525
+ margin-top: 4px;
526
+ }
527
+
528
+ /* 메시지 콘텐츠 내 HTML 스타일링 */
529
+ .message-content h1,
530
+ .message-content h2 {
531
+ color: var(--primary-color);
532
+ margin: 12px 0 8px 0;
533
+ font-weight: 600;
534
+ }
535
+
536
+ .message-content h1 { font-size: 18px; }
537
+ .message-content h2 {
538
+ font-size: 16px;
539
+ border-bottom: 1px solid var(--border-color);
540
+ padding-bottom: 4px;
541
+ }
542
+
543
+ .message-content ul,
544
+ .message-content ol {
545
+ margin: 8px 0;
546
+ padding-left: 20px;
547
+ }
548
+
549
+ .message-content li {
550
+ margin: 4px 0;
551
+ line-height: 1.4;
552
+ }
553
+
554
+ .message-content strong {
555
+ color: var(--primary-color);
556
+ }
557
+
558
+ .message-content p {
559
+ margin: 8px 0;
560
+ line-height: 1.5;
561
+ }
562
+
563
+ .message.user .message-content h1,
564
+ .message.user .message-content h2,
565
+ .message.user .message-content strong {
566
+ color: white;
567
+ }
568
+
569
+ /* 채팅 입력 영역 */
570
+ .chat-input-container {
571
+ padding: 20px 24px;
572
+ background: var(--bg-secondary);
573
+ border-top: 1px solid var(--border-color);
574
+ }
575
+ .chat-input-wrapper {
576
+ display: flex;
577
+ gap: 12px;
578
+ align-items: flex-end;
579
+ }
580
+ .chat-input {
581
+ flex: 1;
582
+ padding: 12px 16px;
583
+ background: var(--bg-tertiary);
584
+ border: 2px solid var(--border-color);
585
+ border-radius: 24px;
586
+ color: var(--text-primary);
587
+ font-size: 14px;
588
+ resize: none;
589
+ max-height: 120px;
590
+ min-height: 44px;
591
+ font-family: inherit;
592
+ transition: border-color 0.2s ease;
593
+ }
594
+ .chat-input:focus {
595
+ outline: none;
596
+ border-color: var(--primary-color);
597
+ }
598
+ .chat-input::placeholder {
599
+ color: var(--text-muted);
600
+ }
601
+ .send-btn {
602
+ width: 44px;
603
+ height: 44px;
604
+ border-radius: 50%;
605
+ background: var(--primary-color);
606
+ border: none;
607
+ color: white;
608
+ cursor: pointer;
609
+ display: flex;
610
+ align-items: center;
611
+ justify-content: center;
612
+ transition: all 0.2s ease;
613
+ flex-shrink: 0;
614
+ }
615
+ .send-btn:hover {
616
+ background: var(--primary-hover);
617
+ transform: scale(1.05);
618
+ }
619
+ .send-btn:active {
620
+ transform: scale(0.95);
621
+ }
622
+ .send-btn:disabled {
623
+ background: var(--bg-quaternary);
624
+ cursor: not-allowed;
625
+ transform: none;
626
+ }
627
+
628
+ /* 로딩 인디케이터 */
629
+ .typing-indicator {
630
+ display: flex;
631
+ gap: 4px;
632
+ padding: 12px 16px;
633
+ background: var(--bg-secondary);
634
+ border-radius: 16px;
635
+ width: fit-content;
636
+ }
637
+ .typing-dot {
638
+ width: 8px;
639
+ height: 8px;
640
+ border-radius: 50%;
641
+ background: var(--text-muted);
642
+ animation: typing 1.4s infinite;
643
+ }
644
+ .typing-dot:nth-child(2) { animation-delay: 0.2s; }
645
+ .typing-dot:nth-child(3) { animation-delay: 0.4s; }
646
+
647
+ @keyframes typing {
648
+ 0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
649
+ 30% { opacity: 1; transform: translateY(-10px); }
650
+ }
651
+
652
+ /* 스크롤바 스타일 */
653
+ .chat-messages::-webkit-scrollbar,
654
+ .filter-panel::-webkit-scrollbar,
655
+ .regulation-list-container::-webkit-scrollbar,
656
+ .regulation-details-list::-webkit-scrollbar {
657
+ width: 8px;
658
+ }
659
+
660
+ .chat-messages::-webkit-scrollbar-track,
661
+ .filter-panel::-webkit-scrollbar-track,
662
+ .regulation-list-container::-webkit-scrollbar-track,
663
+ .regulation-details-list::-webkit-scrollbar-track {
664
+ background: var(--bg-tertiary);
665
+ }
666
+
667
+ .chat-messages::-webkit-scrollbar-thumb,
668
+ .filter-panel::-webkit-scrollbar-thumb,
669
+ .regulation-list-container::-webkit-scrollbar-thumb,
670
+ .regulation-details-list::-webkit-scrollbar-thumb {
671
+ background: var(--bg-quaternary);
672
+ border-radius: 4px;
673
+ }
674
+
675
+ .chat-messages::-webkit-scrollbar-thumb:hover,
676
+ .filter-panel::-webkit-scrollbar-thumb:hover,
677
+ .regulation-list-container::-webkit-scrollbar-thumb:hover,
678
+ .regulation-details-list::-webkit-scrollbar-thumb:hover {
679
+ background: #555;
680
+ }
681
+ </style>
682
+ </head>
683
+
684
+ <body>
685
+ <header class="header">
686
+ <div class="logo">LexiMind</div>
687
+ <nav>
688
+ <ul class="nav">
689
+ <li><a href="#">법규 검색</a></li>
690
+ <li><a href="#">체크리스트 생성</a></li>
691
+ <li><a href="#">유권해석</a></li>
692
+ <li><a href="#">사용문의</a></li>
693
+ </ul>
694
+ </nav>
695
+ </header>
696
+
697
+ <!-- 햄버거 버튼 -->
698
+ <button class="sidebar-toggle" id="sidebarToggle">
699
+ <span class="hamburger"></span>
700
+ </button>
701
+
702
+ <!-- 새 페이지 버튼 -->
703
+ <button class="new-page-btn" id="newPageBtn">
704
+ <span class="plus-icon">+ 새 대화 생성</span>
705
+ </button>
706
+
707
+ <!-- 사이드바 오버레이 -->
708
+ <div class="sidebar-overlay" id="sidebarOverlay"></div>
709
+
710
+ <!-- 사이드바 -->
711
+ <nav class="sidebar" id="sidebar">
712
+ <div class="sidebar-content">
713
+ <h3>과거 이력</h3>
714
+ <div class="date-ranges">
715
+ <a href="/history/202511" class="date-range-item">
716
+ <span class="date-period">2025.11.01 ~ 2025.11.30</span>
717
+ <span class="date-count">검색 15건</span>
718
+ </a>
719
+ <a href="/history/202510" class="date-range-item">
720
+ <span class="date-period">2025.10.01 ~ 2025.10.31</span>
721
+ <span class="date-count">검색 8건</span>
722
+ </a>
723
+ <a href="/history/202509" class="date-range-item">
724
+ <span class="date-period">2025.09.01 ~ 2025.09.30</span>
725
+ <span class="date-count">검색 12건</span>
726
+ </a>
727
+ </div>
728
+ </div>
729
+ </nav>
730
+
731
+ <main class="main">
732
+ <!-- 필터 패널 -->
733
+ <aside class="filter-panel">
734
+ <div class="filter-header">
735
+ <h2 class="filter-title">검색 필터</h2>
736
+ <div class="toggle-group">
737
+ <span class="toggle-label">지역</span>
738
+ <div class="toggle-options">
739
+ <button class="toggle-btn region-toggle" data-value="국내">국내</button>
740
+ <button class="toggle-btn region-toggle" data-value="북미">북미</button>
741
+ <button class="toggle-btn region-toggle" data-value="유럽">유럽</button>
742
+ <button class="toggle-btn region-toggle active" data-value="전체">전체</button>
743
+ </div>
744
+ </div>
745
+ </div>
746
+
747
+ <!-- 상세 법규 리스트 -->
748
+ <div class="regulation-details-container">
749
+ <div class="regulation-details-header">
750
+ <span class="toggle-label">상세 법규 리스트</span>
751
+ <button class="btn btn-load btn-small" onclick="loadDetailedRegulationList()">리스트 불러오기</button>
752
+ </div>
753
+ <div class="regulation-search" id="regulationSearchContainer" style="display: none;">
754
+ <input type="text" id="regulationSearchInput" class="regulation-search-input" placeholder="법규 검색...">
755
+ </div>
756
+ <div class="regulation-details-list" id="regulationDetailsList">
757
+ <p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">
758
+ 리스트 불러오기를 눌러 상세 법규를 확인하세요.
759
+ </p>
760
+ </div>
761
+ </div>
762
+
763
+ <div class="action-buttons">
764
+ <button class="btn btn-secondary" onclick="clearAllSelections()">선택 초기화</button>
765
+ </div>
766
+ </aside>
767
+
768
+ <!-- 채팅 패널 -->
769
+ <section class="chat-panel">
770
+ <div class="chat-header">
771
+ <h1 class="chat-title">자동차 인증 법규 검색
772
+ <span class="title-subtitle">&nbsp&nbsp관련 법규 내용을 검색으로 편리하게 찾아보세요! ※ 모든 결과물은 AI에 의해 생성된 것으로 오류가 있을 수 있습니다</span>
773
+ </h1>
774
+ <div id="statusText" class="status-text">시스템 준비 중...</div>
775
+ </div>
776
+
777
+ <div class="chat-messages" id="chatMessages">
778
+ <div class="message assistant">
779
+ <div class="message-avatar">Lexi</div>
780
+ <div>
781
+ <div class="message-content">
782
+ 안녕하세요! 자동차 인증 법규 검색 도우미입니다.<br>
783
+ 궁금하신 법규 내용을 검색해보세요.<br><br>
784
+ (ex : ISA의 작동 요건을 알려주고, 표로 정리해줘)
785
+ </div>
786
+ <div class="message-time" id="welcomeTime"></div>
787
+ </div>
788
+ </div>
789
+ </div>
790
+
791
+ <!-- 채팅 입력 영역 -->
792
+ <div class="chat-input-container">
793
+ <div class="chat-input-wrapper">
794
+ <textarea
795
+ id="chatInput"
796
+ class="chat-input"
797
+ placeholder="검색하고 싶은 내용을 입력하세요..."
798
+ rows="1"
799
+ ></textarea>
800
+ <button class="send-btn" id="sendBtn" title="전송">
801
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
802
+ <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
803
+ </svg>
804
+ </button>
805
+ </div>
806
+ </div>
807
+ </section>
808
+ </main>
809
+
810
+ <script>
811
+ // ===== 전역 변수 =====
812
+ let selectedRegulations = [];
813
+ let selectedRegions = [];
814
+ let socket;
815
+
816
+ // ===== 초기화 =====
817
+ document.addEventListener('DOMContentLoaded', function() {
818
+ initializeSocket();
819
+ initializeSidebar();
820
+ initializeToggles();
821
+ initializeChat();
822
+ initializeRegulationList();
823
+ setWelcomeTime();
824
+ updateSelectedCount();
825
+ });
826
+
827
+ // 환영 메시지 시간 설정
828
+ function setWelcomeTime() {
829
+ const timeElement = document.getElementById('welcomeTime');
830
+ if (timeElement) {
831
+ const now = new Date();
832
+ timeElement.textContent = formatTime(now);
833
+ }
834
+ }
835
+
836
+ // 시간 포맷팅
837
+ function formatTime(date) {
838
+ const hours = date.getHours().toString().padStart(2, '0');
839
+ const minutes = date.getMinutes().toString().padStart(2, '0');
840
+ return `${hours}:${minutes}`;
841
+ }
842
+
843
+ // ===== Socket.IO 초기화 =====
844
+ function initializeSocket() {
845
+ socket = io();
846
+ socket.on('message', function(data) {
847
+ document.getElementById('statusText').innerText = data.message;
848
+ });
849
+ }
850
+
851
+ // ===== 사이드바 초기화 =====
852
+ function initializeSidebar() {
853
+ const sidebarToggle = document.getElementById('sidebarToggle');
854
+ const sidebar = document.getElementById('sidebar');
855
+ const sidebarOverlay = document.getElementById('sidebarOverlay');
856
+ const newPageBtn = document.getElementById('newPageBtn');
857
+
858
+ sidebarToggle.addEventListener('click', function() {
859
+ const isOpen = sidebar.classList.contains('open');
860
+ if (isOpen) {
861
+ closeSidebar();
862
+ } else {
863
+ openSidebar();
864
+ }
865
+ });
866
+
867
+ sidebarOverlay.addEventListener('click', closeSidebar);
868
+
869
+ document.addEventListener('keydown', function(e) {
870
+ if (e.key === 'Escape') {
871
+ closeSidebar();
872
+ }
873
+ });
874
+
875
+ if (newPageBtn) {
876
+ newPageBtn.addEventListener('click', function() {
877
+ window.open('/', '_blank');
878
+ });
879
+ }
880
+ }
881
+
882
+ function openSidebar() {
883
+ const sidebar = document.getElementById('sidebar');
884
+ const sidebarToggle = document.getElementById('sidebarToggle');
885
+ const sidebarOverlay = document.getElementById('sidebarOverlay');
886
+ const newPageBtn = document.getElementById('newPageBtn');
887
+
888
+ sidebar.classList.add('open');
889
+ sidebarToggle.classList.add('active');
890
+ sidebarOverlay.classList.add('active');
891
+ if (newPageBtn) newPageBtn.classList.add('show');
892
+ }
893
+
894
+ function closeSidebar() {
895
+ const sidebar = document.getElementById('sidebar');
896
+ const sidebarToggle = document.getElementById('sidebarToggle');
897
+ const sidebarOverlay = document.getElementById('sidebarOverlay');
898
+ const newPageBtn = document.getElementById('newPageBtn');
899
+
900
+ sidebar.classList.remove('open');
901
+ sidebarToggle.classList.remove('active');
902
+ sidebarOverlay.classList.remove('active');
903
+ if (newPageBtn) newPageBtn.classList.remove('show');
904
+ }
905
+
906
+ // ===== 토글 버튼 초기화 =====
907
+ function initializeToggles() {
908
+ document.querySelectorAll('.region-toggle').forEach(btn => {
909
+ btn.addEventListener('click', function() {
910
+ handleRegionToggle(this);
911
+ });
912
+ });
913
+ }
914
+
915
+ function handleRegionToggle(clickedBtn) {
916
+ const value = clickedBtn.getAttribute('data-value');
917
+ const allButtons = document.querySelectorAll('.region-toggle');
918
+
919
+ if (value === '전체') {
920
+ // 전체 선택 - 다른 모든 버튼 비활성화
921
+ allButtons.forEach(btn => btn.classList.remove('active'));
922
+ clickedBtn.classList.add('active');
923
+ selectedRegions.length = 0;
924
+ } else {
925
+ // 개별 선택 - 전체 버튼 비활성화
926
+ const allBtn = document.querySelector('.region-toggle[data-value="전체"]');
927
+ if (allBtn) allBtn.classList.remove('active');
928
+
929
+ clickedBtn.classList.toggle('active');
930
+
931
+ if (clickedBtn.classList.contains('active')) {
932
+ if (!selectedRegions.includes(value)) {
933
+ selectedRegions.push(value);
934
+ }
935
+ } else {
936
+ selectedRegions = selectedRegions.filter(region => region !== value);
937
+ }
938
+
939
+ // 아무것도 선택되지 않았으면 전체 활성화
940
+ if (selectedRegions.length === 0 && allBtn) {
941
+ allBtn.classList.add('active');
942
+ }
943
+ }
944
+
945
+ console.log('선택된 지역:', selectedRegions);
946
+ }
947
+
948
+ function getSelectedRegions() {
949
+ return selectedRegions.length > 0 ? selectedRegions : [];
950
+ }
951
+
952
+ // ===== 법규 리스트 초기화 =====
953
+ function initializeRegulationList() {
954
+ const regulationListDiv = document.getElementById('regulationList');
955
+ regulationListDiv.addEventListener('click', function(e) {
956
+ const item = e.target.closest('.regulation-item');
957
+ if (item) {
958
+ toggleRegulationSelection(item);
959
+ }
960
+ });
961
+ }
962
+
963
+ function toggleRegulationSelection(element) {
964
+ const id = element.getAttribute('data-id');
965
+ if (selectedRegulations.includes(id)) {
966
+ selectedRegulations = selectedRegulations.filter(item => item !== id);
967
+ element.classList.remove('selected');
968
+ } else {
969
+ selectedRegulations.push(id);
970
+ element.classList.add('selected');
971
+ }
972
+ updateSelectedCount();
973
+ }
974
+
975
+ function updateSelectedCount() {
976
+ const countElement = document.getElementById('selectedCount');
977
+ if (countElement) {
978
+ countElement.textContent = `선택됨: ${selectedRegulations.length}`;
979
+ }
980
+ }
981
+
982
+ // ===== 채팅 초기화 =====
983
+ function initializeChat() {
984
+ const chatInput = document.getElementById('chatInput');
985
+ const sendBtn = document.getElementById('sendBtn');
986
+
987
+ // 자동 높이 조절
988
+ chatInput.addEventListener('input', function() {
989
+ this.style.height = 'auto';
990
+ this.style.height = Math.min(this.scrollHeight, 120) + 'px';
991
+ });
992
+
993
+ // Enter 키로 전송
994
+ chatInput.addEventListener('keydown', function(e) {
995
+ if (e.key === 'Enter' && !e.shiftKey) {
996
+ e.preventDefault();
997
+ sendMessage();
998
+ }
999
+ });
1000
+
1001
+ // 전송 버튼 클릭
1002
+ sendBtn.addEventListener('click', sendMessage);
1003
+ }
1004
+
1005
+ // ===== 메시지 전송 =====
1006
+ function sendMessage() {
1007
+ const chatInput = document.getElementById('chatInput');
1008
+ const message = chatInput.value.trim();
1009
+ if (!message) return;
1010
+
1011
+ // 사용자 메시지 추가
1012
+ addMessage(message, 'user');
1013
+
1014
+ // 입력창 초기화
1015
+ chatInput.value = '';
1016
+ chatInput.style.height = 'auto';
1017
+
1018
+ // 로딩 표시
1019
+ showTypingIndicator();
1020
+
1021
+ // 서버에 요청
1022
+ const requestData = {
1023
+ query: message,
1024
+ regions: getSelectedRegions(),
1025
+ selectedRegulations: selectedRegulations.map(id => {
1026
+ const element = document.querySelector(`[data-id="${id}"]`);
1027
+ return {
1028
+ id: id,
1029
+ title: element ? element.getAttribute('data-title') || element.textContent.trim() : id
1030
+ };
1031
+ })
1032
+ };
1033
+
1034
+ fetch('/get_message', {
1035
+ method: 'POST',
1036
+ headers: {
1037
+ 'Content-Type': 'application/json'
1038
+ },
1039
+ body: JSON.stringify(requestData)
1040
+ })
1041
+ .then(response => response.json())
1042
+ .then(data => {
1043
+ hideTypingIndicator();
1044
+ if (data.message) {
1045
+ addMessage(data.message, 'assistant');
1046
+ }
1047
+ })
1048
+ .catch(error => {
1049
+ console.error('검색 오류:', error);
1050
+ hideTypingIndicator();
1051
+ addMessage('죄송합니다. 검색 오류가 발생했습니다.', 'assistant');
1052
+ });
1053
+ }
1054
+
1055
+ function addMessage(text, type) {
1056
+ const chatMessages = document.getElementById('chatMessages');
1057
+ const messageDiv = document.createElement('div');
1058
+ messageDiv.className = `message ${type}`;
1059
+
1060
+ const avatar = document.createElement('div');
1061
+ avatar.className = 'message-avatar';
1062
+ avatar.textContent = type === 'user' ? 'Me' : 'Lexi';
1063
+
1064
+ const contentWrapper = document.createElement('div');
1065
+ const content = document.createElement('div');
1066
+ content.className = 'message-content';
1067
+
1068
+ if (type === 'assistant') {
1069
+ const decodedText = decodeHtmlEntities(text);
1070
+ content.innerHTML = decodedText;
1071
+ } else {
1072
+ content.textContent = text;
1073
+ }
1074
+
1075
+ const time = document.createElement('div');
1076
+ time.className = 'message-time';
1077
+ time.textContent = formatTime(new Date());
1078
+
1079
+ contentWrapper.appendChild(content);
1080
+ contentWrapper.appendChild(time);
1081
+ messageDiv.appendChild(avatar);
1082
+ messageDiv.appendChild(contentWrapper);
1083
+ chatMessages.appendChild(messageDiv);
1084
+
1085
+ chatMessages.scrollTop = chatMessages.scrollHeight;
1086
+ }
1087
+
1088
+ function decodeHtmlEntities(text) {
1089
+ const textarea = document.createElement('textarea');
1090
+ textarea.innerHTML = text;
1091
+ return textarea.value;
1092
+ }
1093
+
1094
+ function showTypingIndicator() {
1095
+ const chatMessages = document.getElementById('chatMessages');
1096
+ const indicator = document.createElement('div');
1097
+ indicator.className = 'message assistant';
1098
+ indicator.id = 'typingIndicator';
1099
+
1100
+ const avatar = document.createElement('div');
1101
+ avatar.className = 'message-avatar';
1102
+ avatar.textContent = 'Lexi';
1103
+
1104
+ const typing = document.createElement('div');
1105
+ typing.className = 'typing-indicator';
1106
+ typing.innerHTML = '<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>';
1107
+
1108
+ indicator.appendChild(avatar);
1109
+ indicator.appendChild(typing);
1110
+ chatMessages.appendChild(indicator);
1111
+ chatMessages.scrollTop = chatMessages.scrollHeight;
1112
+ }
1113
+
1114
+ function hideTypingIndicator() {
1115
+ const indicator = document.getElementById('typingIndicator');
1116
+ if (indicator) {
1117
+ indicator.remove();
1118
+ }
1119
+ }
1120
+
1121
+ // ===== 상세 법규 리스트 관련 함수 =====
1122
+ function loadDetailedRegulationList() {
1123
+ const selectedRegions = getSelectedRegions();
1124
+ const detailsListDiv = document.getElementById('regulationDetailsList');
1125
+ const searchContainer = document.getElementById('regulationSearchContainer');
1126
+
1127
+ detailsListDiv.innerHTML = '<p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">상세 법규 리스트 로딩 중...</p>';
1128
+
1129
+ fetch('/get_reg_list', {
1130
+ method: 'POST',
1131
+ headers: {
1132
+ 'Content-Type': 'application/json'
1133
+ },
1134
+ body: JSON.stringify({ regions: selectedRegions })
1135
+ })
1136
+ .then(response => response.json())
1137
+ .then(data => {
1138
+ if (data.reg_list_part) {
1139
+ displayDetailedRegulationList(data.reg_list_part);
1140
+ searchContainer.style.display = 'block';
1141
+ initializeRegulationSearch();
1142
+ } else {
1143
+ detailsListDiv.innerHTML = '<p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">상세 법규 리스트를 불러올 수 없습니다.</p>';
1144
+ }
1145
+ })
1146
+ .catch(error => {
1147
+ console.error('상세 법규 리스트 로딩 오류:', error);
1148
+ detailsListDiv.innerHTML = '<p style="color: #ff6b6b; padding: 20px; text-align: center; margin: 0;">리스트 로딩 중 오류가 발생했습니다.</p>';
1149
+ });
1150
+ }
1151
+
1152
+ function displayDetailedRegulationList(data) {
1153
+ const detailsListDiv = document.getElementById('regulationDetailsList');
1154
+
1155
+ let listHTML = '';
1156
+
1157
+ if (typeof data === 'string') {
1158
+ const lines = data.split('\n').filter(line => line.trim());
1159
+ lines.forEach((line, index) => {
1160
+ const regId = `detail_reg_${Date.now()}_${index}`;
1161
+ const isSelected = selectedRegulations.includes(regId);
1162
+ listHTML += `
1163
+ <div class="regulation-detail-item ${isSelected ? 'selected' : ''}"
1164
+ data-id="${regId}"
1165
+ data-title="${line.trim()}"
1166
+ data-search-text="${line.trim().toLowerCase()}">
1167
+ ${line.trim()}
1168
+ </div>
1169
+ `;
1170
+ });
1171
+ } else if (Array.isArray(data)) {
1172
+ data.forEach((item, index) => {
1173
+ const regId = item.id || `detail_reg_${Date.now()}_${index}`;
1174
+ const title = item.title || item.name || item.toString();
1175
+ const isSelected = selectedRegulations.includes(regId);
1176
+ listHTML += `
1177
+ <div class="regulation-detail-item ${isSelected ? 'selected' : ''}"
1178
+ data-id="${regId}"
1179
+ data-title="${title}"
1180
+ data-search-text="${title.toLowerCase()}">
1181
+ ${title}
1182
+ </div>
1183
+ `;
1184
+ });
1185
+ } else {
1186
+ listHTML = '<p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">상세 법규 데이터를 표시할 수 없습니다.</p>';
1187
+ }
1188
+
1189
+ detailsListDiv.innerHTML = listHTML;
1190
+ updateSelectedCount();
1191
+
1192
+ // 상세 법규 리스트 클릭 이벤트 추가
1193
+ detailsListDiv.addEventListener('click', function(e) {
1194
+ const item = e.target.closest('.regulation-detail-item');
1195
+ if (item) {
1196
+ toggleRegulationSelection(item);
1197
+ }
1198
+ });
1199
+ }
1200
+
1201
+ function initializeRegulationSearch() {
1202
+ const searchInput = document.getElementById('regulationSearchInput');
1203
+
1204
+ searchInput.addEventListener('input', function() {
1205
+ const searchTerm = this.value.toLowerCase().trim();
1206
+ filterRegulationList(searchTerm);
1207
+ });
1208
+ }
1209
+
1210
+ function filterRegulationList(searchTerm) {
1211
+ const detailItems = document.querySelectorAll('.regulation-detail-item');
1212
+
1213
+ detailItems.forEach(item => {
1214
+ const searchText = item.getAttribute('data-search-text') || '';
1215
+
1216
+ if (searchTerm === '' || searchText.includes(searchTerm)) {
1217
+ item.classList.remove('hidden');
1218
+ } else {
1219
+ item.classList.add('hidden');
1220
+ }
1221
+ });
1222
+ }
1223
+
1224
+ function clearAllSelections() {
1225
+ selectedRegulations = [];
1226
+ document.querySelectorAll('.regulation-item').forEach(item => {
1227
+ item.classList.remove('selected');
1228
+ });
1229
+ document.querySelectorAll('.regulation-detail-item').forEach(item => {
1230
+ item.classList.remove('selected');
1231
+ });
1232
+ updateSelectedCount();
1233
+
1234
+ // 검색창 초기화
1235
+ const searchInput = document.getElementById('regulationSearchInput');
1236
+ if (searchInput) {
1237
+ searchInput.value = '';
1238
+ filterRegulationList('');
1239
+ }
1240
+ }
1241
+ </script>
1242
+ </body>
1243
  </html>