Skip to content

Commit a4cff0d

Browse files
authored
Merge pull request #8 from NET-Zero-Inha-Hackers/feat/websocket-chat
Refactor chat page and add WebSocket functionality for real-time mess…
2 parents 7d5ad5c + f57ed8b commit a4cff0d

3 files changed

Lines changed: 243 additions & 178 deletions

File tree

next.config.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import path from 'path';
1+
import path from "path";
22

33
const nextConfig = {
44
webpack(config) {
5-
config.resolve.alias['@'] = path.resolve('./src');
5+
config.resolve.alias["@"] = path.resolve("./src");
66
return config;
77
},
8+
reactStrictMode: false,
89
};
910

1011
export default nextConfig;

src/app/(layout)/chat/page.jsx

Lines changed: 164 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,197 +1,185 @@
1-
'use client';
2-
import { useEffect, useState } from 'react';
3-
import { useRouter } from 'next/navigation';
4-
import Chatlist from '@/components/Chatlist';
5-
import Image from 'next/image';
6-
import ChatMessages from '@/components/ChatMessages';
1+
"use client";
2+
import { useEffect, useState } from "react";
3+
import { useRouter } from "next/navigation";
4+
import Chatlist from "@/components/Chatlist";
5+
import Image from "next/image";
6+
import ChatMessages from "@/components/ChatMessages";
7+
import { newChatWebSocket } from "@/lib/websocket";
78

8-
// const demoChat = {
9-
// chattingId: "chat_1",
10-
// ownerId: "user_1",
11-
// title: "마크다운 형식 예시",
12-
// description: "마크다운 형식으로 작성된 대화 예시",
13-
// chatList: [
14-
// {
15-
// sender: "USER",
16-
// text: "# 마크다운 문법 테스트\n코드와 표를 포함한 다양한 마크다운 예시를 보여주세요.",
17-
// timestamp: 1704880800000,
18-
// model: "",
19-
// use_estimate: 0,
20-
// llm_estimate: 0
21-
// },
22-
// {
23-
// sender: "AI",
24-
// text: "### 코드 예시\n```python\ndef hello_world():\n print('Hello, World!')\n return True\n```\n\n### 표 예시\n| 이름 | 나이 | 직업 |\n|------|------|------|\n| 김철수 | 25 | 개발자 |\n| 이영희 | 28 | 디자이너 |\n\n### 수식 예시\n`E = mc^2`\n\n### 체크리스트\n- [x] 코드 블록\n- [x] 테이블\n- [x] 인라인 코드\n- [ ] 더 필요한 것이 있나요? 있다면 말씀해주세요. 구체적으로 말씀할 수록 정확도는 높아집니다! Optimo와 함께 에너지를 절약해보세요!",
25-
// timestamp: 1704880805000,
26-
// model: "gpt-4",
27-
// use_estimate: 0.002,
28-
// llm_estimate: 0.015
29-
// },
30-
// {
31-
// sender: "USER",
32-
// text: "## 인용문과 링크도 테스트해보고 싶어요!\n\n그리고 이미지는 어떻게 표현하나요?",
33-
// timestamp: 1704880810000,
34-
// model: "",
35-
// use_estimate: 0,
36-
// llm_estimate: 0
37-
// },
38-
// {
39-
// sender: "AI",
40-
// text: "> 이것은 인용문입니다.\n>> 중첩된 인용문도 가능합니다.\n\n[마크다운 가이드](https://www.markdownguide.org)\n\n이미지는 이렇게 표현합니다:\n![마크다운 로고](https://markdown-here.com/img/icon256.png)\n\n***\n\n**굵은 글씨**와 *기울임체*, ~~취소선~~도 가능합니다.\n\n### 목록 표현\n1. 첫 번째\n2. 두 번째\n - 중첩 목록\n - 서브 아이템",
41-
// timestamp: 1704880815000,
42-
// model: "gpt-4",
43-
// use_estimate: 0.002,
44-
// llm_estimate: 0.018
45-
// },
46-
// {
47-
// sender: "USER",
48-
// text: "```javascript\n// 자바스크립트 코드도 테스트해볼까요?\nconst test = () => {\n console.log('테스트');\n}\n```\n\n다른 프로그래밍 언어는 어떻게 표현되나요?",
49-
// timestamp: 1704880820000,
50-
// model: "",
51-
// use_estimate: 0,
52-
// llm_estimate: 0
53-
// },
54-
// {
55-
// sender: "AI",
56-
// text: "여러 프로그래밍 언어 예시입니다:\n\n```java\npublic class Hello {\n public static void main(String[] args) {\n System.out.println(\"안녕하세요\");\n }\n}\n```\n\n```sql\nSELECT * FROM users\nWHERE age > 20\nORDER BY name;\n```\n\n```css\n.markdown-example {\n color: #333;\n font-weight: bold;\n}\n```\n\n### 키보드 입력\n<kbd>Ctrl</kbd> + <kbd>C</kbd>를 눌러 복사하세요.",
57-
// timestamp: 1704880825000,
58-
// model: "gpt-4",
59-
// use_estimate: 0.002,
60-
// llm_estimate: 0.016
61-
// },
62-
// {
63-
// sender: "USER",
64-
// text: "### 수학 수식은 어떻게 표현하나요?\n복잡한 수식도 가능한가요?",
65-
// timestamp: 1704880830000,
66-
// model: "",
67-
// use_estimate: 0,
68-
// llm_estimate: 0
69-
// },
70-
// {
71-
// sender: "AI",
72-
// text: "수학 수식 예시입니다:\n\n인라인 수식: `$f(x) = x^2 + 2x + 1$`\n\n블록 수식:\n```math\n\\sum_{i=1}^{n} i = \\frac{n(n+1)}{2}\n```\n\n행렬:\n```math\n\\begin{bmatrix}\na & b \\\\\nc & d\n\\end{bmatrix}\n```\n\n적분:\n```math\n\\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}\n```\n\n---\n\n> 💡 **참고**: 일부 마크다운 뷰어에서는 수식 렌더링을 지원하지 않을 수 있습니다.",
73-
// timestamp: 1704880835000,
74-
// model: "gpt-4",
75-
// use_estimate: 0.002,
76-
// llm_estimate: 0.020
77-
// }
78-
// ],
79-
// createdAt: 1704880800000,
80-
// modifiedAt: 1704880835000
81-
// };
82-
83-
const demoChat = {
9+
export default function MainPage() {
10+
const router = useRouter();
11+
const [isRedirecting, setIsRedirecting] = useState(true);
12+
const [chat, setChat] = useState({
8413
chattingId: "",
8514
ownerId: "",
8615
title: "",
8716
description: "",
8817
chatList: [],
8918
createdAt: 0,
90-
modifiedAt: 0
91-
};
19+
modifiedAt: 0,
20+
});
9221

93-
export default function MainPage() {
94-
const router = useRouter();
95-
const [isRedirecting, setIsRedirecting] = useState(true);
22+
useEffect(() => {
23+
// localStorage에서 lastchattingid 확인
24+
const lastChattingId = localStorage.getItem("lastchattingid");
9625

97-
useEffect(() => {
98-
// localStorage에서 lastchattingid 확인
99-
const lastChattingId = localStorage.getItem('lastchattingid');
100-
101-
if (lastChattingId) {
102-
// lastchattingid가 있으면 해당 채팅으로 리다이렉트
103-
router.push(`/chat/${lastChattingId}`);
104-
} else {
105-
// 없으면 현재 페이지에서 빈 채팅 화면 표시
106-
setIsRedirecting(false);
107-
}
108-
}, [router]);
26+
if (lastChattingId) {
27+
// lastchattingid가 있으면 해당 채팅으로 리다이렉트
28+
router.push(`/chat/${lastChattingId}`);
29+
} else {
30+
// 없으면 현재 페이지에서 빈 채팅 화면 표시
31+
setIsRedirecting(false);
32+
}
33+
}, [router]);
10934

110-
const [formData, setFormData] = useState({
111-
content: ''
112-
});
35+
const [formData, setFormData] = useState({
36+
content: "",
37+
});
11338

114-
const handleInputChange = (e) => {
115-
const { name, value } = e.target;
116-
117-
// 자동 높이 조절
118-
e.target.style.height = 'auto';
119-
const newHeight = Math.min(200, e.target.scrollHeight); // 최대 200px
120-
e.target.style.height = newHeight + 5 + 'px';
121-
122-
// 스크롤을 맨 아래로 이동
123-
e.target.scrollTop = e.target.scrollHeight;
124-
125-
setFormData(prev => ({
126-
...prev,
127-
[name]: value
128-
}));
129-
};
39+
const handleInputChange = (e) => {
40+
const { name, value } = e.target;
13041

131-
// 메시지 전송 함수
132-
const handleSendMessage = () => {
133-
if (formData.content.trim() === '') return;
134-
135-
// 새 채팅 생성 로직
136-
const newChattingId = `chat_${Date.now()}`;
137-
138-
// localStorage에 새 chattingId 저장
139-
localStorage.setItem('lastchattingid', newChattingId);
140-
141-
// 새 채팅 페이지로 이동
142-
router.push(`/chat/${newChattingId}`);
143-
};
42+
// 자동 높이 조절
43+
e.target.style.height = "auto";
44+
const newHeight = Math.min(200, e.target.scrollHeight); // 최대 200px
45+
e.target.style.height = newHeight + 5 + "px";
46+
47+
// 스크롤을 맨 아래로 이동
48+
e.target.scrollTop = e.target.scrollHeight;
14449

145-
const handleInputKeyDown = (e) => {
146-
if (e.isComposing || (e.nativeEvent && e.nativeEvent.isComposing)) return; // 한글 조합 중에는 무시
147-
if (e.key === 'Enter' && !e.shiftKey) {
148-
e.preventDefault();
149-
handleSendMessage();
150-
}
50+
setFormData((prev) => ({
51+
...prev,
52+
[name]: value,
53+
}));
54+
};
55+
56+
// 메시지 전송 함수
57+
const handleSendMessage = () => {
58+
const userChatElement = {
59+
sender: "USER",
60+
text: formData.content,
61+
timestamp: Math.floor(Date.now() / 1000),
62+
model: "",
63+
use_estimate: 0,
64+
llm_estimate: 0,
15165
};
15266

153-
// 리다이렉트 중일 때 로딩 화면 표시
154-
if (isRedirecting) {
155-
return (
156-
<div className="flex flex-row h-screen">
157-
<Chatlist />
158-
<div className="flex-1 flex items-center justify-center">
159-
<div className="text-white">로딩 중...</div>
160-
</div>
161-
</div>
162-
);
67+
setChat((prev) => ({
68+
...prev,
69+
chatList: [...prev.chatList, userChatElement],
70+
}));
71+
function modelNameHandler(modelName) {
72+
// 모델 이름 처리 로직 (예: 상태 업데이트 등)
73+
const aiChatElemnet = {
74+
sender: "AI",
75+
text: "",
76+
timestamp: Math.floor(Date.now() / 1000),
77+
model: modelName,
78+
use_estimate: 0,
79+
llm_estimate: 0,
80+
};
81+
setChat((prev) => ({
82+
...prev,
83+
chatList: [...prev.chatList, aiChatElemnet],
84+
}));
85+
console.log("모델 이름:", modelName);
86+
}
87+
function modelResponseHandler(id, text) {
88+
// append text to the last AI message
89+
setChat((prev) => {
90+
const updatedChatList = prev.chatList;
91+
const lastIndex = prev.chatList.length - 1;
92+
updatedChatList[lastIndex].text += text;
93+
return {
94+
...prev,
95+
chatList: updatedChatList,
96+
};
97+
});
98+
99+
console.log("모델 응답:", id, text);
100+
}
101+
function metadataHandler(id, title, description) {
102+
// 메타데이터 처리 로직 (예: 상태 업데이트 등)
103+
setChat((prev) => ({
104+
...prev,
105+
chattingId: id,
106+
title: title,
107+
description: description,
108+
}));
109+
// localStorage에 새 chattingId 저장
110+
localStorage.setItem("lastchattingid", id);
111+
console.log("메타데이터:", id, title, description);
163112
}
164113

114+
// WebSocket 연결 설정
115+
const socket = newChatWebSocket(
116+
formData.content,
117+
localStorage.getItem("jwtToken"),
118+
modelNameHandler,
119+
modelResponseHandler,
120+
metadataHandler
121+
);
122+
};
123+
124+
const handleInputKeyDown = (e) => {
125+
if (e.isComposing || (e.nativeEvent && e.nativeEvent.isComposing)) return; // 한글 조합 중에는 무시
126+
if (e.key === "Enter" && !e.shiftKey) {
127+
e.preventDefault();
128+
handleSendMessage();
129+
}
130+
};
131+
132+
// 리다이렉트 중일 때 로딩 화면 표시
133+
if (isRedirecting) {
165134
return (
166-
<div className="flex flex-row h-screen">
167-
<Chatlist />
168-
<div className="flex-1 flex flex-col items-start justify-start p-4">
169-
<div className="text-2xl font-bold pl-6 mb-2">{demoChat.title == "" ? "New Chat" : demoChat.title}</div>
170-
<div className="flex flex-col w-full flex-1 bg-[#3F424A] rounded-lg p-8 min-h-0">
171-
<div id="chat-box" className="flex flex-col flex-1 overflow-y-auto min-h-0">
172-
<ChatMessages
173-
chatList={demoChat.chatList}
174-
/>
175-
</div>
176-
<div id="input-box" className="flex flex-row items-center justify-between bg-[#4b4f5b] rounded-lg text-[#eeeeee] min-h-14 shadow-lg">
177-
<textarea
178-
name="content"
179-
value={formData.content}
180-
onChange={handleInputChange}
181-
onKeyDown={handleInputKeyDown}
182-
className="w-full px-4 py-2 rounded-md resize-none overflow-y-auto bg-transparent outline-none max-h-[150px]"
183-
style={{
184-
maxHeight: '150px'
185-
}}
186-
placeholder="내용을 입력하세요..."
187-
rows={1}
188-
/>
189-
<button className="p-2 hover:bg-[#5b5f6b] rounded-lg transition-colors" onClick={handleSendMessage}>
190-
<Image src="/icon/arrow.svg" alt="전송" width={28} height={28} />
191-
</button>
192-
</div>
193-
</div>
194-
</div>
135+
<div className="flex flex-row h-screen">
136+
<Chatlist />
137+
<div className="flex-1 flex items-center justify-center">
138+
<div className="text-white">로딩 중...</div>
195139
</div>
140+
</div>
196141
);
142+
}
143+
144+
return (
145+
<div className="flex flex-row h-screen">
146+
<Chatlist />
147+
<div className="flex-1 flex flex-col items-start justify-start p-4">
148+
<div className="text-2xl font-bold pl-6 mb-2">
149+
{chat.title == "" ? "New Chat" : chat.title}
150+
</div>
151+
<div className="flex flex-col w-full flex-1 bg-[#3F424A] rounded-lg p-8 min-h-0">
152+
<div
153+
id="chat-box"
154+
className="flex flex-col flex-1 overflow-y-auto min-h-0"
155+
>
156+
<ChatMessages chatList={chat.chatList} />
157+
</div>
158+
<div
159+
id="input-box"
160+
className="flex flex-row items-center justify-between bg-[#4b4f5b] rounded-lg text-[#eeeeee] min-h-14 shadow-lg"
161+
>
162+
<textarea
163+
name="content"
164+
value={formData.content}
165+
onChange={handleInputChange}
166+
onKeyDown={handleInputKeyDown}
167+
className="w-full px-4 py-2 rounded-md resize-none overflow-y-auto bg-transparent outline-none max-h-[150px]"
168+
style={{
169+
maxHeight: "150px",
170+
}}
171+
placeholder="내용을 입력하세요..."
172+
rows={1}
173+
/>
174+
<button
175+
className="p-2 hover:bg-[#5b5f6b] rounded-lg transition-colors"
176+
onClick={handleSendMessage}
177+
>
178+
<Image src="/icon/arrow.svg" alt="전송" width={28} height={28} />
179+
</button>
180+
</div>
181+
</div>
182+
</div>
183+
</div>
184+
);
197185
}

0 commit comments

Comments
 (0)