scipious commited on
Commit
a9abd1a
·
verified ·
1 Parent(s): 6a8c05f

Upload chat_v02.html

Browse files
Files changed (1) hide show
  1. templates/chat_v02.html +1784 -0
templates/chat_v02.html ADDED
@@ -0,0 +1,1784 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ .btn-danger {
372
+ background: #ff4757;
373
+ }
374
+
375
+ .btn-danger:hover {
376
+ background: #ff3838;
377
+ }
378
+
379
+ .regulation-search {
380
+ padding: 12px 24px;
381
+ background: var(--bg-tertiary);
382
+ border-bottom: 1px solid var(--border-color);
383
+ }
384
+ .regulation-search-input {
385
+ width: 100%;
386
+ padding: 8px 12px;
387
+ border: 1px solid var(--border-color);
388
+ border-radius: var(--radius);
389
+ background: var(--bg-quaternary);
390
+ color: var(--text-primary);
391
+ font-size: 13px;
392
+ transition: border-color 0.2s ease;
393
+ }
394
+ .regulation-search-input:focus {
395
+ outline: none;
396
+ border-color: var(--primary-color);
397
+ }
398
+ .regulation-search-input::placeholder {
399
+ color: var(--text-muted);
400
+ }
401
+ .regulation-details-list {
402
+ flex: 1;
403
+ overflow-y: auto;
404
+ padding: 12px 24px;
405
+ }
406
+ .regulation-detail-item {
407
+ padding: 10px 12px;
408
+ margin: 6px 0;
409
+ background: var(--bg-quaternary);
410
+ border-radius: var(--radius);
411
+ cursor: pointer;
412
+ transition: all 0.2s ease;
413
+ border: 2px solid transparent;
414
+ user-select: none;
415
+ font-size: 13px;
416
+ line-height: 1.4;
417
+ }
418
+ .regulation-detail-item:hover {
419
+ background: #3a3a3a;
420
+ border-color: var(--primary-color);
421
+ }
422
+ .regulation-detail-item.selected {
423
+ background: var(--primary-color);
424
+ color: white;
425
+ border-color: var(--primary-hover);
426
+ }
427
+ .regulation-detail-item.hidden {
428
+ display: none;
429
+ }
430
+
431
+ .action-buttons {
432
+ display: flex;
433
+ gap: 12px;
434
+ padding: 16px 24px;
435
+ border-top: 1px solid var(--border-color);
436
+ background: var(--bg-secondary);
437
+ }
438
+
439
+ /* ===== 채팅 패널 ===== */
440
+ .chat-panel {
441
+ flex: 1;
442
+ display: flex;
443
+ flex-direction: column;
444
+ background: var(--bg-primary);
445
+ }
446
+ .chat-header {
447
+ padding: 20px 24px;
448
+ background: var(--bg-secondary);
449
+ border-bottom: 1px solid var(--border-color);
450
+ }
451
+ .chat-title {
452
+ font-size: 18px;
453
+ font-weight: 600;
454
+ color: var(--text-primary);
455
+ margin: 0;
456
+ display: flex;
457
+ align-items: baseline;
458
+ gap: 10px;
459
+ }
460
+ .title-subtitle {
461
+ font-size: 14px;
462
+ font-weight: 400;
463
+ color: var(--text-muted);
464
+ }
465
+
466
+ .status-text {
467
+ margin-top: 12px;
468
+ padding: 16px 20px;
469
+ background: linear-gradient(135deg, var(--bg-tertiary) 0%, var(--bg-quaternary) 100%);
470
+ border: 2px solid var(--border-color);
471
+ border-radius: 12px;
472
+ font-size: 16px;
473
+ font-weight: 600;
474
+ color: var(--primary-color);
475
+ position: relative;
476
+ box-shadow: 0 4px 12px rgba(0, 102, 255, 0.15);
477
+ min-height: 60px;
478
+ display: flex;
479
+ align-items: center;
480
+ transition: all 0.3s ease;
481
+ }
482
+
483
+ .status-text:hover {
484
+ border-color: var(--primary-color);
485
+ box-shadow: 0 6px 20px rgba(0, 102, 255, 0.25);
486
+ }
487
+
488
+
489
+ /* 수정된 progress-bar 스타일 - 더 두껍게 */
490
+ .progress-bar {
491
+ position: absolute;
492
+ bottom: 0;
493
+ left: 0;
494
+ height: 6px;
495
+ background: linear-gradient(90deg, var(--primary-color), var(--primary-hover));
496
+ border-radius: 0 0 10px 10px;
497
+ transition: width 0.3s ease;
498
+ width: 0%;
499
+ box-shadow: 0 2px 8px rgba(0, 102, 255, 0.4);
500
+ }
501
+
502
+ .cancel-search-btn {
503
+ position: absolute;
504
+ right: 16px;
505
+ top: 50%;
506
+ transform: translateY(-50%);
507
+ padding: 8px 16px;
508
+ font-size: 13px;
509
+ font-weight: 600;
510
+ background: var(--warning-color);
511
+ border: 2px solid transparent;
512
+ border-radius: 8px;
513
+ color: white;
514
+ cursor: pointer;
515
+ display: none;
516
+ transition: all 0.3s ease;
517
+ box-shadow: 0 2px 8px rgba(255, 152, 0, 0.3);
518
+ }
519
+
520
+ .cancel-search-btn:hover {
521
+ background: #e68900;
522
+ border-color: #ff9800;
523
+ transform: translateY(-50%) scale(1.05);
524
+ box-shadow: 0 4px 12px rgba(255, 152, 0, 0.4);
525
+ }
526
+
527
+ .cancel-search-btn:active {
528
+ transform: translateY(-50%) scale(0.95);
529
+ }
530
+
531
+ .chat-messages {
532
+ flex: 1;
533
+ overflow-y: auto;
534
+ padding: 24px;
535
+ display: flex;
536
+ flex-direction: column;
537
+ gap: 16px;
538
+ }
539
+
540
+ /* 메시지 버블 */
541
+ .message {
542
+ display: flex;
543
+ gap: 12px;
544
+ max-width: 80%;
545
+ animation: slideIn 0.3s ease;
546
+ }
547
+ @keyframes slideIn {
548
+ from { opacity: 0; transform: translateY(10px); }
549
+ to { opacity: 1; transform: translateY(0); }
550
+ }
551
+ .message.user {
552
+ align-self: flex-end;
553
+ flex-direction: row-reverse;
554
+ }
555
+ .message.assistant {
556
+ align-self: flex-start;
557
+ }
558
+ .message-avatar {
559
+ width: 36px;
560
+ height: 36px;
561
+ border-radius: 50%;
562
+ background: var(--primary-color);
563
+ display: flex;
564
+ align-items: center;
565
+ justify-content: center;
566
+ color: white;
567
+ font-weight: 700;
568
+ font-size: 14px;
569
+ flex-shrink: 0;
570
+ }
571
+ .message.user .message-avatar {
572
+ background: var(--success-color);
573
+ }
574
+ .message-content {
575
+ background: var(--bg-secondary);
576
+ padding: 12px 16px;
577
+ border-radius: 16px;
578
+ color: var(--text-primary);
579
+ line-height: 1.5;
580
+ word-wrap: break-word;
581
+ }
582
+ .message.user .message-content {
583
+ background: var(--primary-color);
584
+ color: white;
585
+ }
586
+ .message-time {
587
+ font-size: 11px;
588
+ color: var(--text-muted);
589
+ margin-top: 4px;
590
+ }
591
+
592
+ /* 메시지 콘텐츠 내 HTML 스타일링 */
593
+ .message-content h1,
594
+ .message-content h2 {
595
+ color: var(--primary-color);
596
+ margin: 12px 0 8px 0;
597
+ font-weight: 600;
598
+ }
599
+
600
+ .message-content h1 { font-size: 18px; }
601
+ .message-content h2 {
602
+ font-size: 16px;
603
+ border-bottom: 1px solid var(--border-color);
604
+ padding-bottom: 4px;
605
+ }
606
+
607
+ .message-content ul,
608
+ .message-content ol {
609
+ margin: 8px 0;
610
+ padding-left: 20px;
611
+ }
612
+
613
+ .message-content li {
614
+ margin: 4px 0;
615
+ line-height: 1.4;
616
+ }
617
+
618
+ .message-content strong {
619
+ color: var(--primary-color);
620
+ }
621
+
622
+ .message-content p {
623
+ margin: 8px 0;
624
+ line-height: 1.5;
625
+ }
626
+
627
+ .message.user .message-content h1,
628
+ .message.user .message-content h2,
629
+ .message.user .message-content strong {
630
+ color: white;
631
+ }
632
+
633
+ /* 채팅 입력 영역 */
634
+ .chat-input-container {
635
+ padding: 20px 24px;
636
+ background: var(--bg-secondary);
637
+ border-top: 1px solid var(--border-color);
638
+ }
639
+ .chat-input-wrapper {
640
+ display: flex;
641
+ gap: 12px;
642
+ align-items: flex-end;
643
+ }
644
+ .chat-input {
645
+ flex: 1;
646
+ padding: 12px 16px;
647
+ background: var(--bg-tertiary);
648
+ border: 2px solid var(--border-color);
649
+ border-radius: 24px;
650
+ color: var(--text-primary);
651
+ font-size: 14px;
652
+ resize: none;
653
+ max-height: 120px;
654
+ min-height: 44px;
655
+ font-family: inherit;
656
+ transition: border-color 0.2s ease;
657
+ }
658
+ .chat-input:focus {
659
+ outline: none;
660
+ border-color: var(--primary-color);
661
+ }
662
+ .chat-input::placeholder {
663
+ color: var(--text-muted);
664
+ }
665
+ .send-btn {
666
+ width: 44px;
667
+ height: 44px;
668
+ border-radius: 50%;
669
+ background: var(--primary-color);
670
+ border: none;
671
+ color: white;
672
+ cursor: pointer;
673
+ display: flex;
674
+ align-items: center;
675
+ justify-content: center;
676
+ transition: all 0.2s ease;
677
+ flex-shrink: 0;
678
+ }
679
+ .send-btn:hover {
680
+ background: var(--primary-hover);
681
+ transform: scale(1.05);
682
+ }
683
+ .send-btn:active {
684
+ transform: scale(0.95);
685
+ }
686
+ .send-btn:disabled {
687
+ background: var(--bg-quaternary);
688
+ cursor: not-allowed;
689
+ transform: none;
690
+ }
691
+
692
+ /* 로딩 인디케이터 */
693
+ .typing-indicator {
694
+ display: flex;
695
+ gap: 4px;
696
+ padding: 12px 16px;
697
+ background: var(--bg-secondary);
698
+ border-radius: 16px;
699
+ width: fit-content;
700
+ }
701
+ .typing-dot {
702
+ width: 8px;
703
+ height: 8px;
704
+ border-radius: 50%;
705
+ background: var(--text-muted);
706
+ animation: typing 1.4s infinite;
707
+ }
708
+ .typing-dot:nth-child(2) { animation-delay: 0.2s; }
709
+ .typing-dot:nth-child(3) { animation-delay: 0.4s; }
710
+
711
+ @keyframes typing {
712
+ 0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
713
+ 30% { opacity: 1; transform: translateY(-10px); }
714
+ }
715
+
716
+ /* 스크롤바 스타일 */
717
+ .chat-messages::-webkit-scrollbar,
718
+ .filter-panel::-webkit-scrollbar,
719
+ .regulation-list-container::-webkit-scrollbar,
720
+ .regulation-details-list::-webkit-scrollbar {
721
+ width: 8px;
722
+ }
723
+
724
+ .chat-messages::-webkit-scrollbar-track,
725
+ .filter-panel::-webkit-scrollbar-track,
726
+ .regulation-list-container::-webkit-scrollbar-track,
727
+ .regulation-details-list::-webkit-scrollbar-track {
728
+ background: var(--bg-tertiary);
729
+ }
730
+
731
+ .chat-messages::-webkit-scrollbar-thumb,
732
+ .filter-panel::-webkit-scrollbar-thumb,
733
+ .regulation-list-container::-webkit-scrollbar-thumb,
734
+ .regulation-details-list::-webkit-scrollbar-thumb {
735
+ background: var(--bg-quaternary);
736
+ border-radius: 4px;
737
+ }
738
+
739
+ .chat-messages::-webkit-scrollbar-thumb:hover,
740
+ .filter-panel::-webkit-scrollbar-thumb:hover,
741
+ .regulation-list-container::-webkit-scrollbar-thumb:hover,
742
+ .regulation-details-list::-webkit-scrollbar-thumb:hover {
743
+ background: #555;
744
+ }
745
+
746
+ .search-mode-group {
747
+ margin-bottom: 20px;
748
+ padding: 16px;
749
+ background: var(--bg-quaternary);
750
+ border-radius: var(--radius);
751
+ border: 1px solid var(--border-color);
752
+ }
753
+
754
+ .search-mode-toggle {
755
+ position: relative;
756
+ display: inline-block;
757
+ width: 60px;
758
+ height: 34px;
759
+ }
760
+
761
+ .search-mode-toggle input {
762
+ opacity: 0;
763
+ width: 0;
764
+ height: 0;
765
+ }
766
+
767
+ .slider {
768
+ position: absolute;
769
+ cursor: pointer;
770
+ top: 0;
771
+ left: 0;
772
+ right: 0;
773
+ bottom: 0;
774
+ background-color: var(--bg-tertiary);
775
+ transition: 0.4s;
776
+ border-radius: 34px;
777
+ border: 2px solid var(--border-color);
778
+ }
779
+
780
+ .slider:before {
781
+ position: absolute;
782
+ content: "";
783
+ height: 24px;
784
+ width: 24px;
785
+ left: 3px;
786
+ bottom: 3px;
787
+ background-color: var(--text-secondary);
788
+ transition: 0.4s;
789
+ border-radius: 50%;
790
+ }
791
+
792
+ input:checked + .slider {
793
+ background-color: var(--primary-color);
794
+ border-color: var(--primary-hover);
795
+ }
796
+
797
+ input:checked + .slider:before {
798
+ transform: translateX(24px);
799
+ background-color: white;
800
+ }
801
+
802
+ .search-mode-label {
803
+ display: flex;
804
+ align-items: center;
805
+ gap: 12px;
806
+ margin-bottom: 8px;
807
+ color: var(--text-secondary);
808
+ font-weight: 600;
809
+ font-size: 14px;
810
+ }
811
+
812
+ .search-mode-description {
813
+ font-size: 12px;
814
+ color: var(--text-muted);
815
+ line-height: 1.4;
816
+ margin-top: 8px;
817
+ }
818
+
819
+ .mode-indicator {
820
+ font-size: 13px;
821
+ font-weight: 600;
822
+ color: var(--primary-color);
823
+ }
824
+
825
+ .regulation-type-tabs {
826
+ display: flex;
827
+ background: var(--bg-tertiary);
828
+ border-bottom: 1px solid var(--border-color);
829
+ padding: 8px 12px;
830
+ gap: 4px;
831
+ }
832
+
833
+ .type-tab {
834
+ padding: 8px 16px;
835
+ background: var(--bg-quaternary);
836
+ border: 1px solid var(--border-color);
837
+ border-radius: var(--radius);
838
+ color: var(--text-secondary);
839
+ cursor: pointer;
840
+ font-size: 12px;
841
+ font-weight: 600;
842
+ transition: all 0.2s ease;
843
+ flex: 1;
844
+ text-align: center;
845
+ }
846
+
847
+ .type-tab:hover {
848
+ background: var(--bg-tertiary);
849
+ color: var(--text-primary);
850
+ }
851
+
852
+ .type-tab.active {
853
+ background: var(--primary-color);
854
+ border-color: var(--primary-hover);
855
+ color: white;
856
+ }
857
+
858
+ /* 기존 스타일에 타입별 구분을 위한 스타일 추가 */
859
+ .regulation-detail-item[data-type="part"]::before {
860
+ content: "[P] ";
861
+ color: var(--primary-color);
862
+ font-weight: 700;
863
+ font-size: 11px;
864
+ }
865
+
866
+ .regulation-detail-item[data-type="section"]::before {
867
+ content: "[S] ";
868
+ color: var(--success-color);
869
+ font-weight: 700;
870
+ font-size: 11px;
871
+ }
872
+
873
+ .regulation-detail-item[data-type="chapter"]::before {
874
+ content: "[C] ";
875
+ color: var(--warning-color);
876
+ font-weight: 700;
877
+ font-size: 11px;
878
+ }
879
+
880
+ .regulation-detail-item[data-type="jo"]::before {
881
+ content: "[조] ";
882
+ color: #ff6b6b;
883
+ font-weight: 700;
884
+ font-size: 11px;
885
+ }
886
+
887
+
888
+
889
+ </style>
890
+ </head>
891
+
892
+ <body>
893
+ <header class="header">
894
+ <div class="logo">LexiMind</div>
895
+ <nav>
896
+ <ul class="nav">
897
+ <li><a href="#">법규 검색</a></li>
898
+ <li><a href="#">체크리스트 생성</a></li>
899
+ <li><a href="#">유권해석</a></li>
900
+ <li><a href="#">사용문의</a></li>
901
+ </ul>
902
+ </nav>
903
+ </header>
904
+
905
+ <!-- 햄버거 버튼 -->
906
+ <button class="sidebar-toggle" id="sidebarToggle">
907
+ <span class="hamburger"></span>
908
+ </button>
909
+
910
+ <!-- 새 페이지 버튼 -->
911
+ <button class="new-page-btn" id="newPageBtn">
912
+ <span class="plus-icon">+ 새 대화 생성</span>
913
+ </button>
914
+
915
+ <!-- 사이드바 오버레이 -->
916
+ <div class="sidebar-overlay" id="sidebarOverlay"></div>
917
+
918
+ <!-- 사이드바 -->
919
+ <nav class="sidebar" id="sidebar">
920
+ <div class="sidebar-content">
921
+ <h3>과거 이력</h3>
922
+ <div class="date-ranges">
923
+ <a href="/history/202511" class="date-range-item">
924
+ <span class="date-period">2025.11.01 ~ 2025.11.30</span>
925
+ <span class="date-count">검색 15건</span>
926
+ </a>
927
+ <a href="/history/202510" class="date-range-item">
928
+ <span class="date-period">2025.10.01 ~ 2025.10.31</span>
929
+ <span class="date-count">검색 8건</span>
930
+ </a>
931
+ <a href="/history/202509" class="date-range-item">
932
+ <span class="date-period">2025.09.01 ~ 2025.09.30</span>
933
+ <span class="date-count">검색 12건</span>
934
+ </a>
935
+ </div>
936
+ </div>
937
+ </nav>
938
+
939
+ <main class="main">
940
+ <!-- 필터 패널 -->
941
+ <aside class="filter-panel">
942
+ <div class="filter-header">
943
+ <h2 class="filter-title">검색 필터</h2>
944
+ <div class="toggle-group">
945
+ <span class="toggle-label">지역</span>
946
+ <div class="toggle-options">
947
+ <button class="toggle-btn region-toggle" data-value="국내">국내</button>
948
+ <button class="toggle-btn region-toggle" data-value="북미">북미</button>
949
+ <button class="toggle-btn region-toggle" data-value="유럽">유럽</button>
950
+ <button class="toggle-btn region-toggle active" data-value="전체">전체</button>
951
+ </div>
952
+ </div>
953
+
954
+ <!-- 검색 모드 토글 추가 -->
955
+ <div class="search-mode-group">
956
+ <div class="search-mode-label">
957
+ <span>선택된 법규 각각에서 검색하기</span>
958
+ <label class="search-mode-toggle">
959
+ <input type="checkbox" id="searchEachModeToggle" checked>
960
+ <span class="slider"></span>
961
+ </label>
962
+ </div>
963
+ <div class="mode-indicator" id="searchModeIndicator">각각 검색 모드</div>
964
+ <div class="search-mode-description" id="searchModeDescription">
965
+ 선택된 각 법규별로 개별 검색을 수행하고 결과를 따로 표시합니다.
966
+ </div>
967
+ </div>
968
+ </div>
969
+
970
+ <!-- 상세 법규 리스트 -->
971
+ <div class="regulation-details-container">
972
+ <div class="regulation-details-header">
973
+ <span class="toggle-label">상세 법규 리스트</span>
974
+ <button class="btn btn-load btn-small" onclick="loadDetailedRegulationList()">리스트 불러오기</button>
975
+ </div>
976
+
977
+ <!-- 법규 타입 선택 탭 추가 -->
978
+ <div class="regulation-type-tabs" id="regulationTypeTabs" style="display: none;">
979
+ <button class="type-tab active" data-type="part">Part</button>
980
+ <button class="type-tab" data-type="section">Section</button>
981
+ <button class="type-tab" data-type="chapter">Chapter</button>
982
+ <button class="type-tab" data-type="jo">조</button>
983
+ </div>
984
+
985
+ <div class="regulation-search" id="regulationSearchContainer" style="display: none;">
986
+ <input type="text" id="regulationSearchInput" class="regulation-search-input" placeholder="법규 검색...">
987
+ </div>
988
+ <div class="regulation-details-list" id="regulationDetailsList">
989
+ <p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">
990
+ 리스트 불러오기를 눌러 상세 법규를 확인하세요.
991
+ </p>
992
+ </div>
993
+ </div>
994
+
995
+ <div class="action-buttons">
996
+ <button class="btn btn-secondary" onclick="clearAllSelections()">선택 초기화</button>
997
+ </div>
998
+ </aside>
999
+
1000
+ <!-- 채팅 패널 -->
1001
+ <section class="chat-panel">
1002
+ <div class="chat-header">
1003
+ <h1 class="chat-title">자동차 인증 법규 검색
1004
+ <span class="title-subtitle">&nbsp&nbsp관련 법규 내용을 검색으로 편리하게 찾아보세요! ※ 모든 결과물은 AI에 의해 생성된 것으로 오류가 있을 수 있습니다</span>
1005
+ </h1>
1006
+ <div id="statusText" class="status-text">
1007
+ 시스템 준비 중...
1008
+ <div class="progress-bar" id="progressBar"></div>
1009
+ <button class="cancel-search-btn" id="cancelSearchBtn" onclick="cancelSearch()">취소</button>
1010
+ </div>
1011
+ </div>
1012
+
1013
+ <div class="chat-messages" id="chatMessages">
1014
+ <div class="message assistant">
1015
+ <div class="message-avatar">Lexi</div>
1016
+ <div>
1017
+ <div class="message-content">
1018
+ 안녕하세요! 자동차 인증 법규 검색 도우미입니다.<br>
1019
+ 궁금하신 법규 내용을 검색해보세요.<br><br>
1020
+ (ex : ISA의 작동 요건을 알려주고, 표로 정리해줘)
1021
+ </div>
1022
+ <div class="message-time" id="welcomeTime"></div>
1023
+ </div>
1024
+ </div>
1025
+ </div>
1026
+
1027
+ <!-- 채팅 입력 영역 -->
1028
+ <div class="chat-input-container">
1029
+ <div class="chat-input-wrapper">
1030
+ <textarea
1031
+ id="chatInput"
1032
+ class="chat-input"
1033
+ placeholder="검색하고 싶은 내용을 입력하세요..."
1034
+ rows="1"
1035
+ ></textarea>
1036
+ <button class="send-btn" id="sendBtn" title="전송">
1037
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1038
+ <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
1039
+ </svg>
1040
+ </button>
1041
+ </div>
1042
+ </div>
1043
+ </section>
1044
+ </main>
1045
+
1046
+ <script>
1047
+ // ===== 전역 변수 =====
1048
+ let selectedRegulations = [];
1049
+ let selectedRegions = [];
1050
+ let socket;
1051
+ let currentSessionId = null;
1052
+ let isSearching = false;
1053
+ let searchEachMode = true;
1054
+ let regulationData = {}; // 모든 법규 데이터 저장
1055
+ let currentRegulationType = 'part'; // 현재 선택된 법규 타입
1056
+
1057
+ // ===== 초기화 =====
1058
+ document.addEventListener('DOMContentLoaded', function() {
1059
+ initializeSocket();
1060
+ initializeSidebar();
1061
+ initializeToggles();
1062
+ initializeChat();
1063
+ initializeRegulationList();
1064
+ initializeSearchModeToggle();
1065
+ initializeRegulationTypeTabs(); // 탭 초기화 추가
1066
+ setWelcomeTime();
1067
+ updateSelectedCount();
1068
+ });
1069
+
1070
+ // ===== 법규 타입 탭 초기화 =====
1071
+ function initializeRegulationTypeTabs() {
1072
+ const tabs = document.querySelectorAll('.type-tab');
1073
+ tabs.forEach(tab => {
1074
+ tab.addEventListener('click', function() {
1075
+ const type = this.getAttribute('data-type');
1076
+ switchRegulationType(type);
1077
+ });
1078
+ });
1079
+ }
1080
+
1081
+ function switchRegulationType(type) {
1082
+ currentRegulationType = type;
1083
+
1084
+ // 탭 활성화 상태 변경
1085
+ document.querySelectorAll('.type-tab').forEach(tab => {
1086
+ tab.classList.remove('active');
1087
+ });
1088
+ document.querySelector(`.type-tab[data-type="${type}"]`).classList.add('active');
1089
+
1090
+ // 해당 타입의 데이터 표시
1091
+ if (regulationData[`reg_list_${type}`]) {
1092
+ displayDetailedRegulationList(regulationData[`reg_list_${type}`], type);
1093
+ }
1094
+
1095
+ console.log('법규 타입 변경:', type);
1096
+ }
1097
+
1098
+ // ===== 검색 모드 토글 초기화 =====
1099
+ function initializeSearchModeToggle() {
1100
+ const searchModeToggle = document.getElementById('searchEachModeToggle');
1101
+ const modeIndicator = document.getElementById('searchModeIndicator');
1102
+ const modeDescription = document.getElementById('searchModeDescription');
1103
+
1104
+ searchModeToggle.addEventListener('change', function() {
1105
+ searchEachMode = this.checked;
1106
+ updateSearchModeDisplay();
1107
+ });
1108
+
1109
+ updateSearchModeDisplay(); // 초기 상태 설정
1110
+ }
1111
+
1112
+ function updateSearchModeDisplay() {
1113
+ const modeIndicator = document.getElementById('searchModeIndicator');
1114
+ const modeDescription = document.getElementById('searchModeDescription');
1115
+
1116
+ if (searchEachMode) {
1117
+ modeIndicator.textContent = '각각 검색 모드';
1118
+ modeDescription.textContent = '선택된 각 법규별로 개별 검색을 수행하고 결과를 따로 표시합니다.';
1119
+ } else {
1120
+ modeIndicator.textContent = '통합 검색 모드';
1121
+ modeDescription.textContent = '선택된 모든 법규를 통합하여 한 번에 검색하고 결과를 종합하여 표시합니다.';
1122
+ }
1123
+
1124
+ console.log('검색 모드 변경:', searchEachMode ? '각각 검색' : '통합 검색');
1125
+ }
1126
+
1127
+ // 환영 메시지 시간 설정
1128
+ function setWelcomeTime() {
1129
+ const timeElement = document.getElementById('welcomeTime');
1130
+ if (timeElement) {
1131
+ const now = new Date();
1132
+ timeElement.textContent = formatTime(now);
1133
+ }
1134
+ }
1135
+
1136
+ // 시간 포맷팅
1137
+ function formatTime(date) {
1138
+ const hours = date.getHours().toString().padStart(2, '0');
1139
+ const minutes = date.getMinutes().toString().padStart(2, '0');
1140
+ return `${hours}:${minutes}`;
1141
+ }
1142
+
1143
+ // ===== Socket.IO 초기화 =====
1144
+ function initializeSocket() {
1145
+ socket = io();
1146
+
1147
+ // 기존 시스템 메시지
1148
+ socket.on('message', function(data) {
1149
+ updateSearchStatus(data.message);
1150
+ });
1151
+
1152
+ // 검색 시작 - session_id 받기 (추가됨)
1153
+ socket.on('search_started', function(data) {
1154
+ currentSessionId = data.session_id;
1155
+ console.log('Search started with session ID:', currentSessionId);
1156
+ });
1157
+
1158
+ // 검색 상태 업데이트
1159
+ socket.on('search_status', function(data) {
1160
+ updateSearchStatus(data.message);
1161
+ if (data.progress !== undefined) {
1162
+ updateProgressBar(data.progress);
1163
+ }
1164
+ });
1165
+
1166
+ // 법규별 결과 수신 (여러 법규 선택 시)
1167
+ socket.on('regulation_result', function(data) {
1168
+ const message = `**${data.regulation_title} (${data.regulation_index}/${data.total_regulations})**\n\n${data.result}`;
1169
+ addMessage(message, 'assistant');
1170
+ });
1171
+
1172
+ // 단일 검색 결과 수신
1173
+ socket.on('search_result', function(data) {
1174
+ addMessage(data.result, 'assistant');
1175
+ });
1176
+
1177
+ // 검색 완료
1178
+ socket.on('search_complete', function(data) {
1179
+ hideTypingIndicator();
1180
+ updateSearchStatus(data.message);
1181
+ enableChatInput();
1182
+ hideCancelButton();
1183
+ isSearching = false;
1184
+ currentSessionId = null; // 세션 ID 초기화
1185
+ });
1186
+
1187
+ // 검색 오류
1188
+ socket.on('search_error', function(data) {
1189
+ hideTypingIndicator();
1190
+ addMessage(`죄송합니다. ${data.message}\n오류 내용: ${data.error}`, 'assistant');
1191
+ enableChatInput();
1192
+ hideCancelButton();
1193
+ isSearching = false;
1194
+ currentSessionId = null; // 세션 ID 초기화
1195
+ });
1196
+
1197
+ // 검색 취소
1198
+ socket.on('search_cancelled', function(data) {
1199
+ hideTypingIndicator();
1200
+ addMessage('검색이 취소되었습니다.', 'assistant');
1201
+ enableChatInput();
1202
+ hideCancelButton();
1203
+ isSearching = false;
1204
+ currentSessionId = null; // 세션 ID 초기화
1205
+ });
1206
+ }
1207
+
1208
+ function updateSearchStatus(message) {
1209
+ const statusElement = document.getElementById('statusText');
1210
+ if (statusElement) {
1211
+ // 첫 번째 텍스트 노드 업데이트
1212
+ const textNode = statusElement.childNodes[0];
1213
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1214
+ textNode.textContent = message;
1215
+ } else {
1216
+ // 텍스트 노드가 없으면 새로 생성
1217
+ statusElement.insertBefore(document.createTextNode(message), statusElement.firstChild);
1218
+ }
1219
+ }
1220
+ }
1221
+
1222
+ function updateProgressBar(progress) {
1223
+ const progressBar = document.getElementById('progressBar');
1224
+ if (progressBar) {
1225
+ progressBar.style.width = `${progress}%`;
1226
+ }
1227
+ }
1228
+
1229
+ function showCancelButton() {
1230
+ const cancelBtn = document.getElementById('cancelSearchBtn');
1231
+ if (cancelBtn) {
1232
+ cancelBtn.style.display = 'block';
1233
+ console.log('Cancel button shown');
1234
+ }
1235
+ }
1236
+
1237
+ function hideCancelButton() {
1238
+ const cancelBtn = document.getElementById('cancelSearchBtn');
1239
+ if (cancelBtn) {
1240
+ cancelBtn.style.display = 'none';
1241
+ console.log('Cancel button hidden');
1242
+ }
1243
+ updateProgressBar(0);
1244
+ }
1245
+
1246
+ function enableChatInput() {
1247
+ const chatInput = document.getElementById('chatInput');
1248
+ const sendBtn = document.getElementById('sendBtn');
1249
+
1250
+ chatInput.disabled = false;
1251
+ sendBtn.disabled = false;
1252
+ chatInput.placeholder = '검색하고 싶은 내용을 입력하세요...';
1253
+ }
1254
+
1255
+ function disableChatInput() {
1256
+ const chatInput = document.getElementById('chatInput');
1257
+ const sendBtn = document.getElementById('sendBtn');
1258
+
1259
+ chatInput.disabled = true;
1260
+ sendBtn.disabled = true;
1261
+ chatInput.placeholder = '검색 진행 중...';
1262
+ }
1263
+
1264
+ // ===== 사이드바 초기화 =====
1265
+ function initializeSidebar() {
1266
+ const sidebarToggle = document.getElementById('sidebarToggle');
1267
+ const sidebar = document.getElementById('sidebar');
1268
+ const sidebarOverlay = document.getElementById('sidebarOverlay');
1269
+ const newPageBtn = document.getElementById('newPageBtn');
1270
+
1271
+ sidebarToggle.addEventListener('click', function() {
1272
+ const isOpen = sidebar.classList.contains('open');
1273
+ if (isOpen) {
1274
+ closeSidebar();
1275
+ } else {
1276
+ openSidebar();
1277
+ }
1278
+ });
1279
+
1280
+ sidebarOverlay.addEventListener('click', closeSidebar);
1281
+
1282
+ document.addEventListener('keydown', function(e) {
1283
+ if (e.key === 'Escape') {
1284
+ closeSidebar();
1285
+ }
1286
+ });
1287
+
1288
+ if (newPageBtn) {
1289
+ newPageBtn.addEventListener('click', function() {
1290
+ window.open('/', '_blank');
1291
+ });
1292
+ }
1293
+ }
1294
+
1295
+ function openSidebar() {
1296
+ const sidebar = document.getElementById('sidebar');
1297
+ const sidebarToggle = document.getElementById('sidebarToggle');
1298
+ const sidebarOverlay = document.getElementById('sidebarOverlay');
1299
+ const newPageBtn = document.getElementById('newPageBtn');
1300
+
1301
+ sidebar.classList.add('open');
1302
+ sidebarToggle.classList.add('active');
1303
+ sidebarOverlay.classList.add('active');
1304
+ if (newPageBtn) newPageBtn.classList.add('show');
1305
+ }
1306
+
1307
+ function closeSidebar() {
1308
+ const sidebar = document.getElementById('sidebar');
1309
+ const sidebarToggle = document.getElementById('sidebarToggle');
1310
+ const sidebarOverlay = document.getElementById('sidebarOverlay');
1311
+ const newPageBtn = document.getElementById('newPageBtn');
1312
+
1313
+ sidebar.classList.remove('open');
1314
+ sidebarToggle.classList.remove('active');
1315
+ sidebarOverlay.classList.remove('active');
1316
+ if (newPageBtn) newPageBtn.classList.remove('show');
1317
+ }
1318
+
1319
+ // ===== 토글 버튼 초기화 =====
1320
+ function initializeToggles() {
1321
+ document.querySelectorAll('.region-toggle').forEach(btn => {
1322
+ btn.addEventListener('click', function() {
1323
+ handleRegionToggle(this);
1324
+ });
1325
+ });
1326
+ }
1327
+
1328
+ function handleRegionToggle(clickedBtn) {
1329
+ const value = clickedBtn.getAttribute('data-value');
1330
+ const allButtons = document.querySelectorAll('.region-toggle');
1331
+
1332
+ if (value === '전체') {
1333
+ // 전체 선택 - 다른 모든 버튼 비활성화
1334
+ allButtons.forEach(btn => btn.classList.remove('active'));
1335
+ clickedBtn.classList.add('active');
1336
+ selectedRegions.length = 0;
1337
+ } else {
1338
+ // 개별 선택 - 전체 버튼 비활성화
1339
+ const allBtn = document.querySelector('.region-toggle[data-value="전체"]');
1340
+ if (allBtn) allBtn.classList.remove('active');
1341
+
1342
+ clickedBtn.classList.toggle('active');
1343
+
1344
+ if (clickedBtn.classList.contains('active')) {
1345
+ if (!selectedRegions.includes(value)) {
1346
+ selectedRegions.push(value);
1347
+ }
1348
+ } else {
1349
+ selectedRegions = selectedRegions.filter(region => region !== value);
1350
+ }
1351
+
1352
+ // 아무것도 선택되지 않았으면 전체 활성화
1353
+ if (selectedRegions.length === 0 && allBtn) {
1354
+ allBtn.classList.add('active');
1355
+ }
1356
+ }
1357
+
1358
+ console.log('선택된 지역:', selectedRegions);
1359
+ }
1360
+
1361
+ function getSelectedRegions() {
1362
+ return selectedRegions.length > 0 ? selectedRegions : [];
1363
+ }
1364
+
1365
+ // ===== 법규 리스트 초기화 =====
1366
+ function initializeRegulationList() {
1367
+ // 이미 상세 법규 리스트에서 처리되므로 여기서는 빈 함수로 유지
1368
+ }
1369
+
1370
+ function toggleRegulationSelection(element) {
1371
+ const id = element.getAttribute('data-id');
1372
+ if (selectedRegulations.includes(id)) {
1373
+ selectedRegulations = selectedRegulations.filter(item => item !== id);
1374
+ element.classList.remove('selected');
1375
+ } else {
1376
+ selectedRegulations.push(id);
1377
+ element.classList.add('selected');
1378
+ }
1379
+ updateSelectedCount();
1380
+ }
1381
+
1382
+ function updateSelectedCount() {
1383
+ const countElement = document.getElementById('selectedCount');
1384
+ if (countElement) {
1385
+ countElement.textContent = `선택됨: ${selectedRegulations.length}`;
1386
+ }
1387
+ }
1388
+
1389
+ // ===== 채팅 초기화 (수정된 버전) =====
1390
+ function initializeChat() {
1391
+ const chatInput = document.getElementById('chatInput');
1392
+ const sendBtn = document.getElementById('sendBtn');
1393
+
1394
+ // 자동 높이 조절
1395
+ chatInput.addEventListener('input', function() {
1396
+ this.style.height = 'auto';
1397
+ this.style.height = Math.min(this.scrollHeight, 120) + 'px';
1398
+ });
1399
+
1400
+ // Enter 키로 전송
1401
+ chatInput.addEventListener('keydown', function(e) {
1402
+ if (e.key === 'Enter' && !e.shiftKey && !isSearching) {
1403
+ e.preventDefault();
1404
+ sendMessage();
1405
+ }
1406
+ });
1407
+
1408
+ // 전송 버튼 클릭
1409
+ sendBtn.addEventListener('click', function() {
1410
+ if (!isSearching) {
1411
+ sendMessage();
1412
+ }
1413
+ });
1414
+ }
1415
+
1416
+ // ===== 메시지 전송 (수정된 버전) =====
1417
+ function sendMessage() {
1418
+ const chatInput = document.getElementById('chatInput');
1419
+ const message = chatInput.value.trim();
1420
+ if (!message || isSearching) return;
1421
+
1422
+ // 사용자 메시지 추가
1423
+ addMessage(message, 'user');
1424
+
1425
+ // 입력창 초기화 및 비활성화
1426
+ chatInput.value = '';
1427
+ chatInput.style.height = 'auto';
1428
+ disableChatInput();
1429
+ isSearching = true;
1430
+
1431
+ // 로딩 표시
1432
+ showTypingIndicator();
1433
+ showCancelButton();
1434
+
1435
+ // 서버에 소켓으로 요청 (검색 모드 포함)
1436
+ const requestData = {
1437
+ query: message,
1438
+ regions: getSelectedRegions(),
1439
+ searchEachMode: searchEachMode, // 검색 모드 추가
1440
+ selectedRegulations: selectedRegulations.map(id => {
1441
+ const element = document.querySelector(`[data-id="${id}"]`);
1442
+ return {
1443
+ id: id,
1444
+ title: element ? element.getAttribute('data-title') || element.textContent.trim() : id
1445
+ };
1446
+ })
1447
+ };
1448
+
1449
+ // 소켓으로 검색 요청 전송
1450
+ socket.emit('search_query', requestData);
1451
+ }
1452
+
1453
+ // 검색 취소 함수 (수정됨)
1454
+ function cancelSearch() {
1455
+ console.log('Cancel search clicked. Session ID:', currentSessionId, 'Is searching:', isSearching);
1456
+
1457
+ if (currentSessionId && isSearching) {
1458
+ socket.emit('cancel_search', { session_id: currentSessionId });
1459
+ console.log('Cancel request sent with session ID:', currentSessionId);
1460
+ } else {
1461
+ console.log('Cannot cancel: no session ID or not searching');
1462
+ // 로컬에서 검색 상태 초기화
1463
+ hideTypingIndicator();
1464
+ enableChatInput();
1465
+ hideCancelButton();
1466
+ isSearching = false;
1467
+ currentSessionId = null;
1468
+ updateSearchStatus('검색이 취소되었습니다.');
1469
+ }
1470
+ }
1471
+
1472
+ function addMessage(text, type) {
1473
+ const chatMessages = document.getElementById('chatMessages');
1474
+ const messageDiv = document.createElement('div');
1475
+ messageDiv.className = `message ${type}`;
1476
+
1477
+ const avatar = document.createElement('div');
1478
+ avatar.className = 'message-avatar';
1479
+ avatar.textContent = type === 'user' ? 'Me' : 'Lexi';
1480
+
1481
+ const contentWrapper = document.createElement('div');
1482
+ const content = document.createElement('div');
1483
+ content.className = 'message-content';
1484
+
1485
+ if (type === 'assistant') {
1486
+ // 마크다운 스타일 텍스트를 HTML로 변환
1487
+ //const formattedText = formatMarkdownToHtml(text);
1488
+ content.innerHTML = text;
1489
+ } else {
1490
+ content.textContent = text;
1491
+ }
1492
+
1493
+ const time = document.createElement('div');
1494
+ time.className = 'message-time';
1495
+ time.textContent = formatTime(new Date());
1496
+
1497
+ contentWrapper.appendChild(content);
1498
+ contentWrapper.appendChild(time);
1499
+ messageDiv.appendChild(avatar);
1500
+ messageDiv.appendChild(contentWrapper);
1501
+
1502
+ // 타이핑 인디케이터가 있으면 그 위에 추가
1503
+ const typingIndicator = document.getElementById('typingIndicator');
1504
+ if (typingIndicator) {
1505
+ chatMessages.insertBefore(messageDiv, typingIndicator);
1506
+ } else {
1507
+ chatMessages.appendChild(messageDiv);
1508
+ }
1509
+
1510
+ chatMessages.scrollTop = chatMessages.scrollHeight;
1511
+ }
1512
+
1513
+ function formatMarkdownToHtml(text) {
1514
+ // 간단한 마크다운 변환
1515
+ let html = text;
1516
+
1517
+ // **굵은 글씨** 변환
1518
+ html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
1519
+
1520
+ // 줄바꿈 변환
1521
+ html = html.replace(/\n/g, '<br>');
1522
+
1523
+ return html;
1524
+ }
1525
+
1526
+ function showTypingIndicator() {
1527
+ // 기존 타이핑 인디케이터 제거
1528
+ hideTypingIndicator();
1529
+
1530
+ const chatMessages = document.getElementById('chatMessages');
1531
+ const indicator = document.createElement('div');
1532
+ indicator.className = 'message assistant';
1533
+ indicator.id = 'typingIndicator';
1534
+
1535
+ const avatar = document.createElement('div');
1536
+ avatar.className = 'message-avatar';
1537
+ avatar.textContent = 'Lexi';
1538
+
1539
+ const typing = document.createElement('div');
1540
+ typing.className = 'typing-indicator';
1541
+ typing.innerHTML = '<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>';
1542
+
1543
+ indicator.appendChild(avatar);
1544
+ indicator.appendChild(typing);
1545
+ chatMessages.appendChild(indicator);
1546
+ chatMessages.scrollTop = chatMessages.scrollHeight;
1547
+ }
1548
+
1549
+ function hideTypingIndicator() {
1550
+ const indicator = document.getElementById('typingIndicator');
1551
+ if (indicator) {
1552
+ indicator.remove();
1553
+ }
1554
+ }
1555
+
1556
+ function decodeHtmlEntities(text) {
1557
+ const textarea = document.createElement('textarea');
1558
+ textarea.innerHTML = text;
1559
+ return textarea.value;
1560
+ }
1561
+
1562
+ // ===== 상세 법규 리스트 관련 함수 =====
1563
+ function loadDetailedRegulationList() {
1564
+ const selectedRegions = getSelectedRegions();
1565
+ const detailsListDiv = document.getElementById('regulationDetailsList');
1566
+ const searchContainer = document.getElementById('regulationSearchContainer');
1567
+ const typeTabs = document.getElementById('regulationTypeTabs');
1568
+
1569
+ detailsListDiv.innerHTML = '<p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">상세 법규 리스트 로딩 중...</p>';
1570
+
1571
+ fetch('/get_reg_list', {
1572
+ method: 'POST',
1573
+ headers: {
1574
+ 'Content-Type': 'application/json'
1575
+ },
1576
+ body: JSON.stringify({ regions: selectedRegions })
1577
+ })
1578
+ .then(response => response.json())
1579
+ .then(data => {
1580
+ // 모든 법규 데이터 저장
1581
+ regulationData = data;
1582
+
1583
+ if (data.reg_list_part || data.reg_list_section || data.reg_list_chapter || data.reg_list_jo) {
1584
+ // 탭과 검색창 표시
1585
+ typeTabs.style.display = 'flex';
1586
+ searchContainer.style.display = 'block';
1587
+
1588
+ // 기본적으로 part 타입 표시
1589
+ switchRegulationType('part');
1590
+ initializeRegulationSearch();
1591
+ } else {
1592
+ detailsListDiv.innerHTML = '<p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">상세 법규 리스트를 불러올 수 없습니다.</p>';
1593
+ }
1594
+ })
1595
+ .catch(error => {
1596
+ console.error('상세 법규 리스트 로딩 오류:', error);
1597
+ detailsListDiv.innerHTML = '<p style="color: #ff6b6b; padding: 20px; text-align: center; margin: 0;">리스트 로딩 중 오류가 발생했습니다.</p>';
1598
+ });
1599
+ }
1600
+
1601
+ function displayDetailedRegulationList(data, type) {
1602
+ const detailsListDiv = document.getElementById('regulationDetailsList');
1603
+
1604
+ let listHTML = '';
1605
+
1606
+ if (typeof data === 'string' && data.trim()) {
1607
+ const lines = data.split('\n').filter(line => line.trim());
1608
+ lines.forEach((line, index) => {
1609
+ const regId = `detail_reg_${type}_${Date.now()}_${index}`;
1610
+ const isSelected = selectedRegulations.includes(regId);
1611
+ listHTML += `
1612
+ <div class="regulation-detail-item ${isSelected ? 'selected' : ''}"
1613
+ data-id="${regId}"
1614
+ data-title="${line.trim()}"
1615
+ data-type="${type}"
1616
+ data-search-text="${line.trim().toLowerCase()}">
1617
+ ${line.trim()}
1618
+ </div>
1619
+ `;
1620
+ });
1621
+ } else if (Array.isArray(data)) {
1622
+ data.forEach((item, index) => {
1623
+ const regId = item.id || `detail_reg_${type}_${Date.now()}_${index}`;
1624
+ const title = item.title || item.name || item.toString();
1625
+ const isSelected = selectedRegulations.includes(regId);
1626
+ listHTML += `
1627
+ <div class="regulation-detail-item ${isSelected ? 'selected' : ''}"
1628
+ data-id="${regId}"
1629
+ data-title="${title}"
1630
+ data-type="${type}"
1631
+ data-search-text="${title.toLowerCase()}">
1632
+ ${title}
1633
+ </div>
1634
+ `;
1635
+ });
1636
+ } else {
1637
+ listHTML = '<p style="color: var(--text-muted); padding: 20px; text-align: center; margin: 0;">해당 타입의 법규 데이터가 없습니다.</p>';
1638
+ }
1639
+
1640
+ detailsListDiv.innerHTML = listHTML;
1641
+ updateSelectedCount();
1642
+
1643
+ // 상세 법규 리스트 클릭 이벤트 추가 (기존 이벤트 리스너 제거 후 재생성)
1644
+ detailsListDiv.removeEventListener('click', handleRegulationClick);
1645
+ detailsListDiv.addEventListener('click', handleRegulationClick);
1646
+ }
1647
+
1648
+ function handleRegulationClick(e) {
1649
+ const item = e.target.closest('.regulation-detail-item');
1650
+ if (item) {
1651
+ toggleRegulationSelection(item);
1652
+ }
1653
+ }
1654
+
1655
+ function toggleRegulationSelection(element) {
1656
+ const id = element.getAttribute('data-id');
1657
+ const isSelected = selectedRegulations.find(reg => reg.id === id);
1658
+
1659
+ if (isSelected) {
1660
+ selectedRegulations = selectedRegulations.filter(reg => reg.id !== id);
1661
+ element.classList.remove('selected');
1662
+ } else {
1663
+ const regulationData = {
1664
+ id: id,
1665
+ title: element.getAttribute('data-title') || element.textContent.trim(),
1666
+ type: element.getAttribute('data-type')
1667
+ };
1668
+ selectedRegulations.push(regulationData);
1669
+ element.classList.add('selected');
1670
+ }
1671
+ updateSelectedCount();
1672
+ }
1673
+
1674
+ function updateSelectedCount() {
1675
+ const countElement = document.getElementById('selectedCount');
1676
+ if (countElement) {
1677
+ countElement.textContent = `선택됨: ${selectedRegulations.length}`;
1678
+ }
1679
+ }
1680
+
1681
+ // ===== 메시지 전송 (수정된 버전) =====
1682
+ function sendMessage() {
1683
+ const chatInput = document.getElementById('chatInput');
1684
+ const message = chatInput.value.trim();
1685
+ if (!message || isSearching) return;
1686
+
1687
+ // 사용자 메시지 추가
1688
+ addMessage(message, 'user');
1689
+
1690
+ // 입력창 초기화 및 비활성화
1691
+ chatInput.value = '';
1692
+ chatInput.style.height = 'auto';
1693
+ disableChatInput();
1694
+ isSearching = true;
1695
+
1696
+ // 로딩 표시
1697
+ showTypingIndicator();
1698
+ showCancelButton();
1699
+
1700
+ // 서버에 소켓으로 요청 (법규 타입 정보 포함)
1701
+ const requestData = {
1702
+ query: message,
1703
+ regions: getSelectedRegions(),
1704
+ searchEachMode: searchEachMode,
1705
+ selectedRegulations: selectedRegulations.map(reg => {
1706
+ return {
1707
+ id: reg.id,
1708
+ title: reg.title,
1709
+ type: reg.type // 법규 타입 정보 추가
1710
+ };
1711
+ })
1712
+ };
1713
+
1714
+ console.log('전송 데이터:', requestData); // 디버깅용
1715
+
1716
+ // 소켓으로 검색 요청 전송
1717
+ socket.emit('search_query', requestData);
1718
+ }
1719
+
1720
+ function clearAllSelections() {
1721
+ selectedRegulations = [];
1722
+ document.querySelectorAll('.regulation-item').forEach(item => {
1723
+ item.classList.remove('selected');
1724
+ });
1725
+ document.querySelectorAll('.regulation-detail-item').forEach(item => {
1726
+ item.classList.remove('selected');
1727
+ });
1728
+ updateSelectedCount();
1729
+
1730
+ // 검색창 초기화
1731
+ const searchInput = document.getElementById('regulationSearchInput');
1732
+ if (searchInput) {
1733
+ searchInput.value = '';
1734
+ filterRegulationList('');
1735
+ }
1736
+ }
1737
+
1738
+ function initializeRegulationSearch() {
1739
+ const searchInput = document.getElementById('regulationSearchInput');
1740
+
1741
+ // 기존 이벤트 리스너 제거 후 새로 추가
1742
+ searchInput.removeEventListener('input', handleSearchInput);
1743
+ searchInput.addEventListener('input', handleSearchInput);
1744
+ }
1745
+
1746
+ function handleSearchInput() {
1747
+ const searchTerm = this.value.toLowerCase().trim();
1748
+ filterRegulationList(searchTerm);
1749
+ }
1750
+
1751
+ function filterRegulationList(searchTerm) {
1752
+ const detailItems = document.querySelectorAll('.regulation-detail-item');
1753
+
1754
+ detailItems.forEach(item => {
1755
+ const searchText = item.getAttribute('data-search-text') || '';
1756
+
1757
+ if (searchTerm === '' || searchText.includes(searchTerm)) {
1758
+ item.classList.remove('hidden');
1759
+ } else {
1760
+ item.classList.add('hidden');
1761
+ }
1762
+ });
1763
+ }
1764
+
1765
+ function clearAllSelections() {
1766
+ selectedRegulations = [];
1767
+ document.querySelectorAll('.regulation-item').forEach(item => {
1768
+ item.classList.remove('selected');
1769
+ });
1770
+ document.querySelectorAll('.regulation-detail-item').forEach(item => {
1771
+ item.classList.remove('selected');
1772
+ });
1773
+ updateSelectedCount();
1774
+
1775
+ // 검색창 초기화
1776
+ const searchInput = document.getElementById('regulationSearchInput');
1777
+ if (searchInput) {
1778
+ searchInput.value = '';
1779
+ filterRegulationList('');
1780
+ }
1781
+ }
1782
+ </script>
1783
+ </body>
1784
+ </html>