Skip to content

Commit 5ee7197

Browse files
authored
Merge pull request #6 from Snowgent/feature/#4-chatting
[FEATURE] 채팅 기능 연결
2 parents afefc18 + d9d1f41 commit 5ee7197

File tree

3 files changed

+141
-7
lines changed

3 files changed

+141
-7
lines changed

src/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
22
import Onboarding from './pages/onboarding/Onboarding';
3-
import Chat from './pages/chat/Chat';
43
import App from './App';
54
import HomePage from './pages/HomePage';
5+
import ChatPageTest from './pages/chat/ChatPageTest';
66

77
const router = createBrowserRouter([
88
{
@@ -17,9 +17,10 @@ const router = createBrowserRouter([
1717
path: 'onboarding',
1818
element: <Onboarding />,
1919
},
20+
2021
{
21-
path: 'chat',
22-
element: <Chat />,
22+
path: 'test',
23+
element: <ChatPageTest />,
2324
},
2425
],
2526
},

src/main.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { StrictMode } from 'react';
1+
// import { StrictMode } from 'react';
22
import { createRoot } from 'react-dom/client';
33
import './index.css';
44
import Router from './index.tsx';
55

66
createRoot(document.getElementById('root')!).render(
7-
<StrictMode>
8-
<Router />
9-
</StrictMode>,
7+
// <StrictMode>
8+
// <Router />
9+
// </StrictMode>,
10+
<Router />,
1011
);

src/pages/chat/ChatPageTest.tsx

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
3+
export default function ChatPageTest() {
4+
const [output, setOutput] = useState('');
5+
const [input, setInput] = useState('');
6+
const [sessionId, setSessionId] = useState<string | null>(null);
7+
const socketRef = useRef<WebSocket | null>(null);
8+
const outputRef = useRef<HTMLTextAreaElement>(null);
9+
10+
const mountedRef = useRef(false);
11+
12+
useEffect(() => {
13+
if (mountedRef.current) return;
14+
mountedRef.current = true;
15+
16+
const socket = new WebSocket('wss://backendbase.site/ws/chat');
17+
socketRef.current = socket;
18+
19+
socket.onopen = () => {
20+
console.log('Connected');
21+
setOutput((prev) => prev + 'Connected\n');
22+
};
23+
24+
socket.onmessage = (event) => {
25+
// bedrock stream done 메시지는 콘솔에만 표시
26+
if (event.data.includes('[bedrock stream done]')) {
27+
console.log('Stream done:', event.data);
28+
return;
29+
}
30+
31+
try {
32+
const json = JSON.parse(event.data);
33+
if (json.type === 'session') {
34+
setSessionId(json.session_id);
35+
setOutput((prev) => prev + `[세션 ID: ${json.session_id}]\n`);
36+
return;
37+
}
38+
} catch {
39+
/* not JSON */
40+
}
41+
setOutput((prev) => prev + event.data + '\n');
42+
};
43+
44+
socket.onerror = (error) => {
45+
console.error('WebSocket Error:', error);
46+
setOutput((prev) => prev + 'Error occurred\n');
47+
};
48+
49+
socket.onclose = (event) => {
50+
console.log('Disconnected:', event.code, event.reason);
51+
setOutput((prev) => prev + `Disconnected: ${event.code}\n`);
52+
};
53+
54+
return () => {
55+
socket.close();
56+
};
57+
}, []);
58+
59+
// 자동 스크롤
60+
useEffect(() => {
61+
if (outputRef.current) {
62+
outputRef.current.scrollTop = outputRef.current.scrollHeight;
63+
}
64+
}, [output]);
65+
66+
const sendMessage = () => {
67+
const text = input.trim();
68+
if (!text || !socketRef.current) return;
69+
70+
if (socketRef.current.readyState === WebSocket.OPEN) {
71+
socketRef.current.send(JSON.stringify({ role: 'user', content: text }));
72+
setInput('');
73+
} else {
74+
setOutput((prev) => prev + 'WebSocket이 연결되지 않음\n');
75+
}
76+
};
77+
78+
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
79+
if (e.key === 'Enter') {
80+
sendMessage();
81+
}
82+
};
83+
84+
return (
85+
<div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
86+
<h3>Chat Test</h3>
87+
{sessionId && <p style={{ color: '#666' }}>세션 ID: {sessionId}</p>}
88+
<textarea
89+
ref={outputRef}
90+
value={output}
91+
readOnly
92+
rows={15}
93+
style={{
94+
width: '100%',
95+
marginBottom: '10px',
96+
padding: '10px',
97+
border: '1px solid #ccc',
98+
borderRadius: '4px',
99+
fontFamily: 'monospace',
100+
}}
101+
/>
102+
<br />
103+
<div style={{ display: 'flex', gap: '10px' }}>
104+
<input
105+
value={input}
106+
onChange={(e) => setInput(e.target.value)}
107+
onKeyPress={handleKeyPress}
108+
placeholder="메시지를 입력하세요"
109+
style={{
110+
flex: 1,
111+
padding: '8px',
112+
border: '1px solid #ccc',
113+
borderRadius: '4px',
114+
}}
115+
/>
116+
<button
117+
onClick={sendMessage}
118+
style={{
119+
padding: '8px 20px',
120+
background: '#007bff',
121+
color: 'white',
122+
border: 'none',
123+
borderRadius: '4px',
124+
cursor: 'pointer',
125+
}}
126+
>
127+
보내기
128+
</button>
129+
</div>
130+
</div>
131+
);
132+
}

0 commit comments

Comments
 (0)