서론 : 피드백 기능 자체는 쉽게 만들었으나
다른 코드 위에 이식하느라 애를 좀 먹었다.
일단 프론트엔드 부터 바로 가자.
1. 프론트엔드
피드백 버튼 생성 및 초기 피드백:
function addMessage(message, isUser, audioData) {
const messageDiv = document.createElement("div");
messageDiv.className = `message ${isUser ? "user-message" : "bot-message"}`;
const messageBubble = document.createElement("div");
messageBubble.className = "message-bubble";
messageBubble.textContent = message;
messageDiv.appendChild(messageBubble);
if (isUser) {
// 피드백 버튼 추가
const feedbackBtn = document.createElement("button");
feedbackBtn.className = "feedback-btn";
feedbackBtn.innerHTML = "💭";
feedbackBtn.onclick = () => getFeedback(message, messageDiv);
messageDiv.appendChild(feedbackBtn);
// 초기 피드백 요청
getInitialFeedback(message, messageDiv);
}
if (!isUser) {
const translateBtn = document.createElement("button");
translateBtn.className = "material-icons translate-icon";
translateBtn.textContent = "translate";
translateBtn.onclick = () => translateMessage(message, messageDiv, translateBtn);
messageDiv.appendChild(translateBtn);
}
elements.chatContainer.appendChild(messageDiv);
elements.chatContainer.scrollTop = elements.chatContainer.scrollHeight;
if (!isUser && audioData) {
playAudio(audioData);
}
return messageDiv;
}
- 메시지를 추가할 때 사용자 메시지인 경우 피드백 버튼(💭)을 추가한다
- 사용자 메세지 옆에 💭가 뜨고 이를 GPT-40-mini로 검사하여 비문인지 아닌지 어색한지 아닌지 판별
getInitialFeedback()
함수를 호출하여 초기 피드백을 요청.
주요 함수들:
function updateMessageEmoji(messageDiv, emoji) {
const feedbackBtn = messageDiv.querySelector(".feedback-btn");
if (feedbackBtn) {
feedbackBtn.innerHTML = emoji;
}
}
async function getFeedback(message, messageDiv) {
try {
const response = await fetch("/feedback", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest" // AJAX 요청임을 명시
},
body: JSON.stringify({ message: message }),
credentials: 'same-origin' // 쿠키 포함
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const contentType = response.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) {
throw new TypeError("응답이 JSON 형식이 아닙니다!");
}
const data = await response.json();
if (data.success) {
showFeedbackModal(data.feedback);
} else {
throw new Error(data.error || "알 수 없는 오류가 발생했습니다.");
}
} catch (error) {
console.error("피드백 요청 오류:", error);
showError(`피드백을 가져오는 중 오류가 발생했습니다: ${error.message}`);
}
}
async function getInitialFeedback(message, messageDiv) {
try {
const response = await fetch("/feedback", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
},
body: JSON.stringify({ message: message }),
credentials: 'same-origin'
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.success) {
updateMessageEmoji(messageDiv, data.feedback.emoji);
}
} catch (error) {
console.error("초기 피드백 요청 오류:", error);
// 초기 피드백 실패는 조용히 처리
updateMessageEmoji(messageDiv, "💭");
}
}
function showFeedbackModal(feedback) {
const existingModal = document.querySelector(".feedback-modal");
if (existingModal) {
existingModal.remove();
}
const modal = document.createElement("div");
modal.className = "feedback-modal";
modal.innerHTML = `
<div class="feedback-modal-content">
<div class="feedback-modal-header">
<h3>Sentence Feedback ${feedback.emoji}</h3>
<button class="close-modal">×</button>
</div>
<div class="feedback-modal-body">
<div class="feedback-section original">
<strong>Original Sentence:</strong>
<p>${feedback.original}</p>
</div>
${feedback.errors.length > 0 ? `
<div class="feedback-section errors">
<strong>Errors Found:</strong>
<ul>
${feedback.errors.map(error => `<li>${error}</li>`).join('')}
</ul>
</div>
` : ''}
<div class="feedback-section corrected">
<strong>Corrected Sentence:</strong>
<p>${feedback.corrected}</p>
</div>
<div class="feedback-section explanation">
<strong>Explanation:</strong>
<p>${feedback.explanation}</p>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
setTimeout(() => modal.classList.add("show"), 10);
modal.querySelector(".close-modal").onclick = () => {
modal.classList.remove("show");
setTimeout(() => modal.remove(), 300);
};
modal.onclick = (e) => {
if (e.target === modal) {
modal.classList.remove("show");
setTimeout(() => modal.remove(), 300);
}
};
}
뭐 엄청 복잡해 보이지만 별거 없음
이모지 변경 / 서버 호출 / 피드백 모달 표시 딱 이 정도임
1. updateMessageEmoji()
: 메시지의 피드백 이모지를 업데이트
- 역할: 메시지 옆의 이모지를 바꾸는 단순한 함수
- 작동방식:
- 메시지 div에서 피드백 버튼을 찾아서
- 그 버튼의 이모지를 새로운 이모지로 교체
- 예시:
- 처음에는 "💭" 표시
- 분석 후 맞으면 "😊", 틀리면 "😢"로 변경
단순하게 사용자의 문장이 맞았는지 틀렸는지 이모티콘으로 피드백을 주는 기능임
메인 작동 방식은 3번에 있
2. getFeedback()
: 서버에 피드백을 요청하고 모달로 표시
- 역할: 사용자가 피드백 버튼(💭)을 클릭했을 때 실행되는 메인 함수
- 작동순서:
- 서버에 사용자의 메시지를 보내서 분석 요청
- 서버가 응답을 제대로 줬는지 확인
- 응답이 올바른 형식(JSON)인지 체크
- 분석 결과를 모달창으로 보여줌
- 에러처리:
- 서버 연결 실패
- 잘못된 응답 형식
- 기타 오류들을 사용자에게 알려줌
세세한 피드백을 서버에 요청하고 이를 Json 형식으로 받아옴
그 결과를 모달창으로 보여줌
3. getInitialFeedback()
: 초기 피드백을 요청하여 이모지 업데이트
- 역할: 사용자가 메시지를 보낼 때 자동으로 실행되는 초기 분석 함수
- 작동방식:
- getFeedback과 비슷하게 서버에 분석 요청
- 하지만 모달창은 띄우지 않고
- 이모지만 업데이트 (맞으면 😊, 틀리면 😢)
- 특징:
- 실패해도 조용히 처리 (사용자에게 오류 알림 없음)
- 기본 이모지(💭)로 유지
단순한 이모티콘 처리 과정
4. showFeedbackModal()
: 피드백 결과를 모달로 표시
- 역할: 피드백 결과를 예쁜 모달창으로 보여주는 함수
- 표시내용:
- 원본 문장
- 발견된 오류 목록
- 수정된 문장
- 자세한 설명
- 특징:
- 애니메이션 효과 (아래에서 위로 슬라이드)
- 닫기 버튼
- 모달 바깥 클릭 시 자동으로 닫힘
- 이전 모달이 있으면 자동으로 제거
단순하게 사용자에게 구체적인 피드백 모달을 표시하는 코드임
큰 흐름
- 메시지 입력 → 초기 피드백(이모지)
- 피드백 버튼 클릭 → 상세 분석
- 분석 결과 → 모달창 표시
- 모달창 닫기 → 다시 기본 상태로
한계
가 있다면 지금 이모티콘은 같은 이모티콘으로 사용자에게 보이지만
사실 서로 다른 요청에서 온 이모티콘이라는 점.
구체적인 피드백 요청을 하게 되면 속도가 조금 느려지기 때문에
이런 식으로 구현했음.
이모티콘 피드백은 속도가 생명이라고 생각함.
추후에 이를 하나의 요청으로 통합하면서 속도를 개선하는 방식 혹은
애니메이션 추가로 사용자의 경험을 해치지 않는 선에서 개선할 생각
2. 백엔드 (app.py)
피드백 API 엔드포인트:
@app.route('/feedback', methods=['POST'])
@login_required
def get_feedback():
try:
message = request.json['message']
if not hasattr(app, 'openai_client'):
app.openai_client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
response = app.openai_client.chat.completions.create(
model="gpt-4o-mini", # 더 빠른 응답을 위해 모델 변경
messages=[
{
"role": "system",
"content": """
Analyze the given Korean sentence and provide feedback in English.
Respond in the following JSON format:
{
"is_correct": boolean,
"original": "original Korean sentence",
"errors": ["error1 in English", "error2 in English"],
"corrected": "corrected Korean sentence",
"explanation": "detailed explanation in English about what was wrong and how it was corrected",
"emoji": "😊" for perfect, "😢" for needs improvement
}
Keep the explanation clear and helpful for English speakers learning Korean.
"""
},
{"role": "user", "content": message}
],
temperature=0.3 # 더 일관된 응답을 위해 temperature 낮춤
)
feedback = json.loads(response.choices[0].message.content)
return jsonify({
'success': True,
'feedback': feedback
})
/feedback
엔드포인트에서 GPT-4o-min를 사용하여 문장 분석- 다음 정보를 포함한 피드백 생성:
- 문장의 정확성 여부
- 원본 문장
- 오류 목록
- 수정된 문장
- 상세 설명
- 이모지 평가
구체적인 피드백을 받아오는 핵심 코드임
JSON 형식으로 받고 이를 프론트에 넘기면 사용자에게 보여주는 방식
3. CSS
사실 CSS는 해석에서 빼려고 했는데
피드백 모달의 직관성과 가시성을 높이기 위해 꽤 시간을 많이 투자해서
적어놨음.
다음과 같은 디자인으로 사용자에게 보이게 됨.
"나는 보라쉑이 좋아." 라는 문장을
"나는 보라색이 좋아." 로 수정해 주는 피드백 모달임.
피드백 관련 스타일:
.feedback-btn {
cursor: pointer;
color: #666;
font-size: 1.2rem;
margin: 0 8px;
transition: all 0.3s ease;
background: none;
border: none;
padding: 4px;
opacity: 0.7;
}
.feedback-btn:hover {
opacity: 1;
color: #4caf50;
transform: scale(1.1);
}
.user-message .feedback-btn {
margin-right: 0;
margin-left: 8px;
}
- 피드백 버튼의 기본 스타일링
- 호버 효과
- 위치 조정
피드백 모달 스타일:
.feedback-modal {
position: fixed;
bottom: -100%;
left: 0;
width: 100%;
height: auto;
max-height: 80vh;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: flex-end;
z-index: 1000;
transition: bottom 0.3s ease-out;
}
.feedback-modal.show {
bottom: 0;
}
.feedback-modal-content {
background: white;
width: 100%;
max-width: 480px;
border-radius: 20px 20px 0 0;
padding: 20px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
}
.feedback-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.feedback-modal-header h3 {
margin: 0;
font-size: 1.2rem;
}
.close-modal {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: 0;
color: #666;
}
.feedback-section {
margin-bottom: 20px;
padding: 15px;
border-radius: 12px;
border-left: 4px solid;
}
/* 원본 문장 섹션 */
.feedback-section.original {
background-color: #f8f9fa;
}
- 모달 창의 레이아웃과 애니메이션
- 피드백 섹션별 스타일링
- 반응형 디자인
정리 (데이터 흐름):
- 사용자가 메시지를 보내면 피드백 버튼(💭)이 자동으로 추가됨.
- 초기에
getInitialFeedback()
이 실행되어 간단한 이모지 피드백을 표시. - 사용자가 피드백 버튼을 클릭하면:
getFeedback()
함수가 실행됨- 서버의
/feedback
엔드포인트로 요청 - GPT-4o-mini가 문장을 분석하여 상세 피드백 생성
- 결과가 모달 창으로 표시됨
- 모든 피드백 데이터는 JSON 형식으로 주고받으며, 사용자의 로그인이 필요함 (
@login_required
데코레이터 사용).
사실상 속도 관리 빼고는 손볼 게 없지만서도 손볼게 아주 많은 기능인 것 같다.
유저 피드백을 기반으로 다시 손볼 예정.
'Min' 카테고리의 다른 글
서비스 코드 분석 (JS 불필요한 함수/변수 최적화) (0) | 2024.11.22 |
---|---|
옵시디언 PARA 메모법 (2) | 2024.11.19 |
현재까지 개발 일지 2024-11-05 (0) | 2024.11.07 |
유저 피드백에 의한 기능 개선/수정 (0) | 2024.11.01 |
2024-10-29 개발일지 (메모리 기능 구현) (1) | 2024.10.29 |