seawolf2357 commited on
Commit
10391c6
·
verified ·
1 Parent(s): 96996bd

Delete index.html

Browse files
Files changed (1) hide show
  1. index.html +0 -638
index.html DELETED
@@ -1,638 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="ko">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>MOUSE 챗</title>
8
- <style>
9
- :root {
10
- --primary-color: #6f42c1;
11
- --secondary-color: #563d7c;
12
- --dark-bg: #121212;
13
- --card-bg: #1e1e1e;
14
- --text-color: #f8f9fa;
15
- --border-color: #333;
16
- --hover-color: #8a5cf6;
17
- }
18
-
19
- body {
20
- font-family: "SF Pro Display", -apple-system, BlinkMacSystemFont, sans-serif;
21
- background-color: var(--dark-bg);
22
- color: var(--text-color);
23
- margin: 0;
24
- padding: 0;
25
- height: 100vh;
26
- display: flex;
27
- flex-direction: column;
28
- overflow: hidden;
29
- }
30
-
31
- .container {
32
- max-width: 900px;
33
- margin: 0 auto;
34
- padding: 20px;
35
- flex-grow: 1;
36
- display: flex;
37
- flex-direction: column;
38
- width: 100%;
39
- }
40
-
41
- .header {
42
- text-align: center;
43
- padding: 20px 0;
44
- border-bottom: 1px solid var(--border-color);
45
- margin-bottom: 20px;
46
- }
47
-
48
- .logo {
49
- display: flex;
50
- align-items: center;
51
- justify-content: center;
52
- gap: 10px;
53
- }
54
-
55
- .logo h1 {
56
- margin: 0;
57
- background: linear-gradient(135deg, var(--primary-color), #a78bfa);
58
- -webkit-background-clip: text;
59
- background-clip: text;
60
- color: transparent;
61
- font-size: 32px;
62
- letter-spacing: 1px;
63
- }
64
-
65
- .chat-container {
66
- border-radius: 12px;
67
- background-color: var(--card-bg);
68
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
69
- padding: 20px;
70
- flex-grow: 1;
71
- display: flex;
72
- flex-direction: column;
73
- border: 1px solid var(--border-color);
74
- }
75
-
76
- .chat-messages {
77
- flex-grow: 1;
78
- overflow-y: auto;
79
- margin-bottom: 20px;
80
- padding: 10px;
81
- scrollbar-width: thin;
82
- scrollbar-color: var(--primary-color) var(--card-bg);
83
- }
84
-
85
- .chat-messages::-webkit-scrollbar {
86
- width: 6px;
87
- }
88
-
89
- .chat-messages::-webkit-scrollbar-thumb {
90
- background-color: var(--primary-color);
91
- border-radius: 6px;
92
- }
93
-
94
- .message {
95
- margin-bottom: 20px;
96
- padding: 14px;
97
- border-radius: 8px;
98
- font-size: 16px;
99
- line-height: 1.6;
100
- position: relative;
101
- max-width: 80%;
102
- animation: fade-in 0.3s ease-out;
103
- }
104
-
105
- @keyframes fade-in {
106
- from {
107
- opacity: 0;
108
- transform: translateY(10px);
109
- }
110
- to {
111
- opacity: 1;
112
- transform: translateY(0);
113
- }
114
- }
115
-
116
- .message.user {
117
- background: linear-gradient(135deg, #2c3e50, #34495e);
118
- margin-left: auto;
119
- border-bottom-right-radius: 2px;
120
- }
121
-
122
- .message.assistant {
123
- background: linear-gradient(135deg, var(--secondary-color), var(--primary-color));
124
- margin-right: auto;
125
- border-bottom-left-radius: 2px;
126
- }
127
-
128
- .controls {
129
- text-align: center;
130
- margin-top: 20px;
131
- display: flex;
132
- justify-content: center;
133
- }
134
-
135
- button {
136
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
137
- color: white;
138
- border: none;
139
- padding: 14px 28px;
140
- font-family: inherit;
141
- font-size: 16px;
142
- cursor: pointer;
143
- transition: all 0.3s;
144
- text-transform: uppercase;
145
- letter-spacing: 1px;
146
- border-radius: 50px;
147
- display: flex;
148
- align-items: center;
149
- justify-content: center;
150
- gap: 10px;
151
- box-shadow: 0 4px 10px rgba(111, 66, 193, 0.3);
152
- }
153
-
154
- button:hover {
155
- transform: translateY(-2px);
156
- box-shadow: 0 6px 15px rgba(111, 66, 193, 0.5);
157
- background: linear-gradient(135deg, var(--hover-color), var(--primary-color));
158
- }
159
-
160
- button:active {
161
- transform: translateY(1px);
162
- }
163
-
164
- #audio-output {
165
- display: none;
166
- }
167
-
168
- .icon-with-spinner {
169
- display: flex;
170
- align-items: center;
171
- justify-content: center;
172
- gap: 12px;
173
- min-width: 180px;
174
- }
175
-
176
- .spinner {
177
- width: 20px;
178
- height: 20px;
179
- border: 2px solid #ffffff;
180
- border-top-color: transparent;
181
- border-radius: 50%;
182
- animation: spin 1s linear infinite;
183
- flex-shrink: 0;
184
- }
185
-
186
- @keyframes spin {
187
- to {
188
- transform: rotate(360deg);
189
- }
190
- }
191
-
192
- .audio-visualizer {
193
- display: flex;
194
- align-items: center;
195
- justify-content: center;
196
- gap: 5px;
197
- min-width: 80px;
198
- height: 25px;
199
- }
200
-
201
- .visualizer-bar {
202
- width: 4px;
203
- height: 100%;
204
- background-color: rgba(255, 255, 255, 0.7);
205
- border-radius: 2px;
206
- transform-origin: bottom;
207
- transform: scaleY(0.1);
208
- transition: transform 0.1s ease;
209
- }
210
-
211
- /* Add styles for toast notifications */
212
- .toast {
213
- position: fixed;
214
- top: 20px;
215
- left: 50%;
216
- transform: translateX(-50%);
217
- padding: 16px 24px;
218
- border-radius: 8px;
219
- font-size: 14px;
220
- z-index: 1000;
221
- display: none;
222
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
223
- }
224
-
225
- .toast.error {
226
- background-color: #f44336;
227
- color: white;
228
- }
229
-
230
- .toast.warning {
231
- background-color: #ff9800;
232
- color: white;
233
- }
234
-
235
- /* Status indicator */
236
- .status-indicator {
237
- display: inline-flex;
238
- align-items: center;
239
- margin-top: 10px;
240
- font-size: 14px;
241
- color: #aaa;
242
- }
243
-
244
- .status-dot {
245
- width: 8px;
246
- height: 8px;
247
- border-radius: 50%;
248
- margin-right: 8px;
249
- }
250
-
251
- .status-dot.connected {
252
- background-color: #4caf50;
253
- }
254
-
255
- .status-dot.disconnected {
256
- background-color: #f44336;
257
- }
258
-
259
- .status-dot.connecting {
260
- background-color: #ff9800;
261
- animation: pulse 1.5s infinite;
262
- }
263
-
264
- @keyframes pulse {
265
- 0% {
266
- opacity: 0.6;
267
- }
268
- 50% {
269
- opacity: 1;
270
- }
271
- 100% {
272
- opacity: 0.6;
273
- }
274
- }
275
-
276
- /* Mouse logo animation */
277
- .mouse-logo {
278
- position: relative;
279
- width: 40px;
280
- height: 40px;
281
- }
282
-
283
- .mouse-ears {
284
- position: absolute;
285
- width: 15px;
286
- height: 15px;
287
- background-color: var(--primary-color);
288
- border-radius: 50%;
289
- }
290
-
291
- .mouse-ear-left {
292
- top: 0;
293
- left: 5px;
294
- }
295
-
296
- .mouse-ear-right {
297
- top: 0;
298
- right: 5px;
299
- }
300
-
301
- .mouse-face {
302
- position: absolute;
303
- top: 10px;
304
- left: 5px;
305
- width: 30px;
306
- height: 30px;
307
- background-color: var(--secondary-color);
308
- border-radius: 50%;
309
- }
310
- </style>
311
- </head>
312
-
313
- <body>
314
- <!-- Add toast element after body opening tag -->
315
- <div id="error-toast" class="toast"></div>
316
- <div class="container">
317
- <div class="header">
318
- <div class="logo">
319
- <div class="mouse-logo">
320
- <div class="mouse-ears mouse-ear-left"></div>
321
- <div class="mouse-ears mouse-ear-right"></div>
322
- <div class="mouse-face"></div>
323
- </div>
324
- <h1>MOUSE 챗</h1>
325
- </div>
326
- <div class="status-indicator">
327
- <div id="status-dot" class="status-dot disconnected"></div>
328
- <span id="status-text">연결 대기 중</span>
329
- </div>
330
- </div>
331
- <div class="chat-container">
332
- <div class="chat-messages" id="chat-messages"></div>
333
- </div>
334
- <div class="controls">
335
- <button id="start-button">대화 시작</button>
336
- </div>
337
- </div>
338
- <audio id="audio-output"></audio>
339
-
340
- <script>
341
- let peerConnection;
342
- let webrtc_id;
343
- const audioOutput = document.getElementById('audio-output');
344
- const startButton = document.getElementById('start-button');
345
- const chatMessages = document.getElementById('chat-messages');
346
- const statusDot = document.getElementById('status-dot');
347
- const statusText = document.getElementById('status-text');
348
- let audioLevel = 0;
349
- let animationFrame;
350
- let audioContext, analyser, audioSource;
351
-
352
- function updateStatus(state) {
353
- statusDot.className = 'status-dot ' + state;
354
- if (state === 'connected') {
355
- statusText.textContent = '연결됨';
356
- } else if (state === 'connecting') {
357
- statusText.textContent = '연결 중...';
358
- } else {
359
- statusText.textContent = '연결 대기 중';
360
- }
361
- }
362
-
363
- function updateButtonState() {
364
- const button = document.getElementById('start-button');
365
- if (peerConnection && (peerConnection.connectionState === 'connecting' || peerConnection.connectionState === 'new')) {
366
- button.innerHTML = `
367
- <div class="icon-with-spinner">
368
- <div class="spinner"></div>
369
- <span>연결 중...</span>
370
- </div>
371
- `;
372
- updateStatus('connecting');
373
- } else if (peerConnection && peerConnection.connectionState === 'connected') {
374
- button.innerHTML = `
375
- <div class="icon-with-spinner">
376
- <div class="audio-visualizer" id="audio-visualizer">
377
- <div class="visualizer-bar"></div>
378
- <div class="visualizer-bar"></div>
379
- <div class="visualizer-bar"></div>
380
- <div class="visualizer-bar"></div>
381
- <div class="visualizer-bar"></div>
382
- </div>
383
- <span>대화 종료</span>
384
- </div>
385
- `;
386
- updateStatus('connected');
387
- } else {
388
- button.innerHTML = '대화 시작';
389
- updateStatus('disconnected');
390
- }
391
- }
392
-
393
- function setupAudioVisualization(stream) {
394
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
395
- analyser = audioContext.createAnalyser();
396
- audioSource = audioContext.createMediaStreamSource(stream);
397
- audioSource.connect(analyser);
398
- analyser.fftSize = 256;
399
- const bufferLength = analyser.frequencyBinCount;
400
- const dataArray = new Uint8Array(bufferLength);
401
-
402
- const visualizerBars = document.querySelectorAll('.visualizer-bar');
403
- const barCount = visualizerBars.length;
404
-
405
- function updateAudioLevel() {
406
- analyser.getByteFrequencyData(dataArray);
407
-
408
- // Calculate levels for each bar from different frequency ranges
409
- for (let i = 0; i < barCount; i++) {
410
- // Divide frequency data into segments for each bar
411
- const start = Math.floor(i * (bufferLength / barCount));
412
- const end = Math.floor((i + 1) * (bufferLength / barCount));
413
-
414
- let sum = 0;
415
- for (let j = start; j < end; j++) {
416
- sum += dataArray[j];
417
- }
418
-
419
- const average = sum / (end - start) / 255;
420
- // Add some minimum height and scale effect
421
- const scaleY = 0.1 + average * 0.9;
422
- visualizerBars[i].style.transform = `scaleY(${scaleY})`;
423
- }
424
-
425
- animationFrame = requestAnimationFrame(updateAudioLevel);
426
- }
427
-
428
- updateAudioLevel();
429
- }
430
-
431
- function showError(message) {
432
- const toast = document.getElementById('error-toast');
433
- toast.textContent = message;
434
- toast.className = 'toast error';
435
- toast.style.display = 'block';
436
- // Hide toast after 5 seconds
437
- setTimeout(() => {
438
- toast.style.display = 'none';
439
- }, 5000);
440
- }
441
-
442
- async function setupWebRTC() {
443
- const config = __RTC_CONFIGURATION__;
444
- peerConnection = new RTCPeerConnection(config);
445
-
446
- // 연결 재시도 메커니즘 추가
447
- let retryCount = 0;
448
- const maxRetries = 3;
449
-
450
- const timeoutId = setTimeout(() => {
451
- const toast = document.getElementById('error-toast');
452
- toast.textContent = "연결이 평소보다 오래 걸리고 있습니다. VPN을 사용 중이신가요?";
453
- toast.className = 'toast warning';
454
- toast.style.display = 'block';
455
- // Hide warning after 5 seconds
456
- setTimeout(() => {
457
- toast.style.display = 'none';
458
- }, 5000);
459
- }, 5000);
460
-
461
- try {
462
- const stream = await navigator.mediaDevices.getUserMedia({
463
- audio: true
464
- });
465
- setupAudioVisualization(stream);
466
- stream.getTracks().forEach(track => {
467
- peerConnection.addTrack(track, stream);
468
- });
469
- peerConnection.addEventListener('track', (evt) => {
470
- if (audioOutput.srcObject !== evt.streams[0]) {
471
- audioOutput.srcObject = evt.streams[0];
472
- audioOutput.play();
473
- }
474
- });
475
- const dataChannel = peerConnection.createDataChannel('text');
476
- dataChannel.onmessage = (event) => {
477
- const eventJson = JSON.parse(event.data);
478
- if (eventJson.type === "error") {
479
- showError(eventJson.message);
480
- }
481
- };
482
- const offer = await peerConnection.createOffer();
483
- await peerConnection.setLocalDescription(offer);
484
- await new Promise((resolve) => {
485
- if (peerConnection.iceGatheringState === "complete") {
486
- resolve();
487
- } else {
488
- const checkState = () => {
489
- if (peerConnection.iceGatheringState === "complete") {
490
- peerConnection.removeEventListener("icegatheringstatechange", checkState);
491
- resolve();
492
- }
493
- };
494
- peerConnection.addEventListener("icegatheringstatechange", checkState);
495
- }
496
- });
497
- peerConnection.addEventListener('connectionstatechange', () => {
498
- console.log('connectionstatechange', peerConnection.connectionState);
499
- if (peerConnection.connectionState === 'connected') {
500
- clearTimeout(timeoutId);
501
- const toast = document.getElementById('error-toast');
502
- toast.style.display = 'none';
503
- retryCount = 0; // 연결 성공 시 재시도 카운트 초기화
504
- } else if (peerConnection.connectionState === 'failed' || peerConnection.connectionState === 'disconnected') {
505
- if (retryCount < maxRetries) {
506
- retryCount++;
507
- console.log(`연결 실패, 재시도 중... (${retryCount}/${maxRetries})`);
508
-
509
- // 현재 연결 정리
510
- if (peerConnection) {
511
- peerConnection.close();
512
- }
513
-
514
- // 잠시 후 재연결 시도
515
- setTimeout(() => {
516
- if (peerConnection && peerConnection.connectionState !== 'connected') {
517
- showError(`연결 재시도 중... (${retryCount}/${maxRetries})`);
518
- setupWebRTC();
519
- }
520
- }, 2000); // 2초 후 재시도
521
- } else {
522
- showError('연결에 실패했습니다. 브라우저를 새로고침 후 다시 시도해주세요.');
523
- }
524
- }
525
- updateButtonState();
526
- });
527
- webrtc_id = Math.random().toString(36).substring(7);
528
- const response = await fetch('/webrtc/offer', {
529
- method: 'POST',
530
- headers: { 'Content-Type': 'application/json' },
531
- body: JSON.stringify({
532
- sdp: peerConnection.localDescription.sdp,
533
- type: peerConnection.localDescription.type,
534
- webrtc_id: webrtc_id
535
- })
536
- });
537
- const serverResponse = await response.json();
538
- if (serverResponse.status === 'failed') {
539
- showError(serverResponse.meta.error === 'concurrency_limit_reached'
540
- ? `너무 많은 연결입니다. 최대 한도는 ${serverResponse.meta.limit} 입니다.`
541
- : serverResponse.meta.error);
542
- stop();
543
- return;
544
- }
545
- await peerConnection.setRemoteDescription(serverResponse);
546
- // 이전 EventSource가 있으면 닫기
547
- if (window.currentEventSource) {
548
- window.currentEventSource.close();
549
- }
550
-
551
- const eventSource = new EventSource('/outputs?webrtc_id=' + webrtc_id);
552
- window.currentEventSource = eventSource; // 전역으로 저장하여 나중에 정리할 수 있도록 함
553
-
554
- eventSource.addEventListener("output", (event) => {
555
- const eventJson = JSON.parse(event.data);
556
- addMessage("assistant", eventJson.content);
557
- });
558
-
559
- eventSource.addEventListener("error", (event) => {
560
- console.error("EventSource 오류:", event);
561
- if (event.target.readyState === EventSource.CLOSED) {
562
- console.log("EventSource 연결이 닫혔습니다.");
563
- }
564
- });
565
- } catch (err) {
566
- clearTimeout(timeoutId);
567
- console.error('Error setting up WebRTC:', err);
568
- showError('연결을 설정하지 못했습니다. 다시 시도해 주세요.');
569
- stop();
570
- }
571
- }
572
-
573
- function addMessage(role, content) {
574
- const messageDiv = document.createElement('div');
575
- messageDiv.classList.add('message', role);
576
- messageDiv.textContent = content;
577
- chatMessages.appendChild(messageDiv);
578
- chatMessages.scrollTop = chatMessages.scrollHeight;
579
- }
580
-
581
- function stop() {
582
- if (animationFrame) {
583
- cancelAnimationFrame(animationFrame);
584
- }
585
- if (audioContext) {
586
- audioContext.close();
587
- audioContext = null;
588
- analyser = null;
589
- audioSource = null;
590
- }
591
- if (peerConnection) {
592
- try {
593
- if (peerConnection.getTransceivers) {
594
- peerConnection.getTransceivers().forEach(transceiver => {
595
- if (transceiver.stop) {
596
- transceiver.stop();
597
- }
598
- });
599
- }
600
- if (peerConnection.getSenders) {
601
- peerConnection.getSenders().forEach(sender => {
602
- if (sender.track && sender.track.stop) sender.track.stop();
603
- });
604
- }
605
- console.log('closing connection');
606
- peerConnection.close();
607
- } catch (error) {
608
- console.error('Error during connection cleanup:', error);
609
- }
610
-
611
- // 클린업 진행 중 오류가 발생하더라도 null로 설정하여 새 연결이 가능하게 함
612
- peerConnection = null;
613
- }
614
-
615
- // EventSource 닫기 (있는 경우)
616
- if (window.currentEventSource) {
617
- window.currentEventSource.close();
618
- window.currentEventSource = null;
619
- }
620
-
621
- updateButtonState();
622
- audioLevel = 0;
623
- }
624
-
625
- startButton.addEventListener('click', () => {
626
- console.log('clicked');
627
- console.log(peerConnection, peerConnection?.connectionState);
628
- if (!peerConnection || peerConnection.connectionState !== 'connected') {
629
- setupWebRTC();
630
- } else {
631
- console.log('stopping');
632
- stop();
633
- }
634
- });
635
- </script>
636
- </body>
637
-
638
- </html>