Skip to content

Commit

Permalink
feat: 스프레드 시트에 대화 로그 저장
Browse files Browse the repository at this point in the history
  • Loading branch information
sangeun0612 committed Oct 19, 2024
1 parent cdb0a48 commit ce2cc8e
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 67 deletions.
4 changes: 1 addition & 3 deletions backend/callUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ async function callUser(phoneNumber) {
await client.calls.create({
url: `https://welfarebot.kr/voice?phoneNumber=${encodeURIComponent(phoneNumber)}&gptRequest=${encodeURIComponent(gptRequest)}`,
to: phoneNumber,
from: '+12566699723',
statusCallback: `https://welfarebot.kr/call-completed?phoneNumber=${encodeURIComponent(phoneNumber)}`,
statusCallbackEvent: ['completed'],
from: '+12566699723'
});
}

Expand Down
2 changes: 1 addition & 1 deletion backend/chatModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async function getChatModelResponse(phoneNumber, gptRequest) {
content: gptContent,
});

console.log("대화 모델 로그: ", conversations);
// console.log("대화 모델 로그: ", conversations);
return gptContent;
}

Expand Down
4 changes: 2 additions & 2 deletions backend/chatSummaryModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async function getChatSummaryModelResponse(phoneNumber) {
// 해당 사용자의 대화 요약 기록 저장
allConversationsMap.set(phoneNumber, conversations);

console.log("대화 요약 모델 로그: ", allConversationsMap.get(phoneNumber));
// console.log("대화 요약 모델 로그: ", allConversationsMap.get(phoneNumber));

return gptContent;
}
Expand All @@ -73,4 +73,4 @@ function resetChatSummaryModelConversations(phoneNumber) {
conversationsMap.delete(phoneNumber);
}

module.exports = {getChatSummaryModelResponse, resetChatSummaryModelConversations};
module.exports = {allConversationsMap, getChatSummaryModelResponse, resetChatSummaryModelConversations};
56 changes: 24 additions & 32 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,21 +182,6 @@ app.post("/voice", async (req, res) => {
res.send(twiml.toString());
});


// 전화 종료
app.post('/call-completed', (req, res) => {
const { phoneNumber } = req.query;

// 전화 상태 초기화
if (activeCalls.has(phoneNumber)) {
activeCalls.delete(phoneNumber);
console.log("\n", phoneNumber, " 전화 상태 초기화");
}

res.status(200).send(`${phoneNumber} 전화 상태 초기화`);
});


// API 경로 외의 모든 요청을 index.html로 라우팅
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../frontend/build', 'index.html'));
Expand Down Expand Up @@ -328,46 +313,53 @@ httpsServer.on('upgrade', (request, socket, head) => {
isAudioProcessing = false;
recognizeStream = null;
break;


// 전화가 종료됐을 때: 대화 내용 요약, 스프레드 시트에 대화 내역 저장, 사용자 정보 초기화
case "stop":
console.log("\n전화 종료");

(async () => {
const chatSummaryModelResponse = await getChatSummaryModelResponse(phoneNumber);
console.log("\n대화 내용 요약:", chatSummaryModelResponse);
if (wsReactConnection && wsReactConnection.readyState === WebSocket.OPEN) {
wsReactConnection.send(JSON.stringify({ event: 'chatSummary', chatSummaryModelResponse }));
}

console.log("\n전화 종료");

// 전화 연결 상태 및 음성 처리 상태 초기화
isFirstCalling = true;
isAudioProcessing = false;


// 스프레드 시트에 대화 내역 저장
try {
await saveDataToSpreadsheets(phoneNumber);
wsReactConnection.send(JSON.stringify({ event: 'toast', message: '상담 데이터가 스프레드시트에 성공적으로 저장되었습니다!' }));
} catch(error) {
console.error("스프레드 시트에 데이터 저장 중 오류 발생: ", error);
}

// 각 모델의 대화 기록 초기화
resetChatModelConversations(phoneNumber);
resetSttCorrectionModelConversations(phoneNumber);
resetChatSummaryModelConversations(phoneNumber);

// STT 스트림 초기화
if (recognizeStream) {
recognizeStream.destroy(); // STT 스트림 종료
recognizeStream = null; // STT 스트림을 null로 설정
}

// 타이머 및 기타 상태 초기화

// 전화 연결 상태 및 음성 처리 상태 초기화
isAudioProcessing = false;

// 타이머 및 기타 상태 초기화
if (timeoutHandle) {
clearTimeout(timeoutHandle);
timeoutHandle = null; // 타이머 초기화
}

console.log("\n사용자 변수 초기화 완료");
}

// 전화 상태 초기화
activeCalls.delete(phoneNumber);
console.log(`\n대화 내용 저장 및 사용자 정보 초기화 완료(사용자: ${phoneNumber})`);
}) ();
break;
}
});

// 연결 종료 처리
wsTwilio.on('close', () => {});
});
}
else {
Expand Down
102 changes: 78 additions & 24 deletions backend/spreadsheet.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,98 @@
const { GoogleSpreadsheet } = require('google-spreadsheet');

const client = new TextToSpeechClient({
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
});

const { GoogleAuth } = require('google-auth-library');
const credentials = require(process.env.GOOGLE_APPLICATION_CREDENTIALS);
const {conversationsMap} = require("./chatModel");
const {allConversationsMap: sttConversationsMap } = require("./sttCorrectionModel");
const {allConversationsMap: summaryConversationsMap } = require("./chatSummaryModel");

const auth = new GoogleAuth({
credentials: {
client_email: credentials.client_email,
private_key: credentials.private_key.replace(/\\n/g, '\n'),
},
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
})

let authClient;
(async () => {
authClient = await auth.getClient();
})();


// 각 모델에 대한 스프레드시트 ID
const SPREADSHEET_ID_CHAT = 'your-chat-model-sheet-id';
const SPREADSHEET_ID_STT = 'your-stt-correction-sheet-id';
const SPREADSHEET_ID_SUMMARY = 'your-summary-model-sheet-id';
const SPREADSHEET_ID = '19dxCszQR6OVHtgLWKMUM5F5-lz2Lx-nNpIi3owRObfE';

// 최대 열 개수 설정
const MAX_COLUMNS = 30;

// 스프레드시트에 데이터를 저장하는 함수
async function saveDataToSpreadsheets(){
async function saveDataToSpreadsheets(phoneNumber){
try{
const chatData = conversationsMap.get(phoneNumber)?.map(conv => conv.content).join('\n') || '';
const sttData = sttConversationsMap.get(phoneNumber)?.flatMap(conv => conv.map(c => c.content)).join('\n') || '';
const summaryData = summaryConversationsMap.get(phoneNumber)?.map(conv => conv.content).join('\n') || '';
const chatDataArray = conversationsMap.get(phoneNumber)?.map(conv => conv.content) || [];
const sttDataArray = sttConversationsMap.get(phoneNumber)?.flatMap(conv => conv.map(c => c.content)) || [];
const summaryDataArray = summaryConversationsMap.get(phoneNumber)?.map(conv => conv.content) || [];

// 각 모델의 데이터를 스프레드시트에 저장
await saveToSpreadsheet(SPREADSHEET_ID_CHAT, chatData);
await saveToSpreadsheet(SPREADSHEET_ID_STT, sttData);
await saveToSpreadsheet(SPREADSHEET_ID_SUMMARY, summaryData);
console.log(`스프레드 시트에 데이터 저장 완료(사용자: ${phoneNumber})`);
// 각 모델의 데이터를 해당 시트에 저장
await saveToSpreadsheet('chat', chatDataArray);
await saveToSpreadsheet('stt', sttDataArray);
await saveToSpreadsheet('summary', summaryDataArray);
} catch (error) {
console.error('스프레드시트 저장 오류(사용자: ${phoneNumber}):', error);
res.status(500).send('스프레드시트 저장 오류(사용자: ${phoneNumber})');
console.error(`스프레드시트 저장 오류(사용자: ${phoneNumber}):`, error);
}
}

// 특정 스프레드시트에 데이터를 저장하는 함수
async function saveToSpreadsheet(sheetId, data) {
const doc = new GoogleSpreadsheet(sheetId);
await doc.useServiceAccountAuth(credentials);
async function saveToSpreadsheet(sheetName, dataArray) {
const doc = new GoogleSpreadsheet(SPREADSHEET_ID);
doc.auth = authClient; // 인증 클라이언트 설정
await doc.loadInfo(); // 문서 정보 로드

const sheet = doc.sheetsByIndex[0]; // 첫 번째 시트를 사용
await sheet.addRow({ content: data });
let sheet = doc.sheetsByTitle[sheetName]; // 시트 이름으로 접근

const headers = generateHeaderValues();

if (!sheet) {
// 시트가 없으면 생성하고 헤더 행 설정
sheet = await doc.addSheet({
title: sheetName,
headerValues: headers,
gridProperties: {
columnCount: MAX_COLUMNS,
},
});
} else {
// 시트의 열 수 확인 및 확장
if (sheet.gridProperties.columnCount < MAX_COLUMNS) {
await sheet.resize({ columnCount: MAX_COLUMNS });
}

try {
// 헤더 행 로드 시도
await sheet.loadHeaderRow();
} catch (error) {
// 헤더 행이 없으면 설정
await sheet.setHeaderRow(headers);
}
}

// 데이터 객체 생성
const row = {};
const length = Math.min(dataArray.length, MAX_COLUMNS);
for (let i = 0; i < length; i++) {
row[`content${i + 1}`] = dataArray[i];
}

// 데이터 추가
await sheet.addRow(row);
console.log(`시트 ${sheetName}에 데이터 추가 완료`);
}

function generateHeaderValues() {
const headers = [];
for (let i = 0; i < MAX_COLUMNS; i++) {
headers.push(`content${i + 1}`);
}
return headers;
}

module.exports = { saveDataToSpreadsheets };
4 changes: 2 additions & 2 deletions backend/sttCorrectionModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async function getSttCorrectionModelResponse(phoneNumber, gptRequest, chatModelR
userConversations.push(conversations);
allConversationsMap.set(phoneNumber, userConversations);

console.log("stt 교정 모델 로그: ", allConversationsMap.get(phoneNumber));
// console.log("stt 교정 모델 로그: ", allConversationsMap.get(phoneNumber));
return gptContent;
}

Expand All @@ -71,4 +71,4 @@ function resetSttCorrectionModelConversations(phoneNumber) {
allConversationsMap.delete(phoneNumber);
}

module.exports = {getSttCorrectionModelResponse, resetSttCorrectionModelConversations};
module.exports = {allConversationsMap,getSttCorrectionModelResponse, resetSttCorrectionModelConversations};
12 changes: 9 additions & 3 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import './App.css';
import { ToastContainer, toast } from 'react-toastify'; // Toast 컴포넌트 임포트
import 'react-toastify/dist/ReactToastify.css'; // Toast 스타일 임포트
import botIcon from './logo_black.png';
import userIcon from './user.png';

Expand All @@ -14,15 +16,15 @@ function App() {
const [crisisTypes, setCrisisTypes] = useState(['', '', '']); // 위기 유형
const [socket, setSocket] = useState(null);
const [messages, setMessages] = useState([]);

const chatContainerRef = useRef(null);

useEffect(() => {
// DynamoDB에서 이름 목록 가져오기
fetch('/api/getPhoneNumbers')
.then(response => response.json())
.then(data => setPhoneNumbers(data))
.catch(error => console.error('이름 불러오기 오류:', error));
.catch(error => console.error('전화번호 불러오기 오류:', error));
}, []);

// 이름 선택 시 위기 유형 가져오기
Expand Down Expand Up @@ -108,11 +110,13 @@ function App() {
...prevMessages,
{type:'gpt', text: `대화 내용 요약: ${data.chatSummaryModelResponse}`, isChatSummary: true },
]);
} else if(data.event === 'toast') {
toast.success(data.message);
}
};

socket.onclose = () => {
console.log('WebSocket 연결 종료');
console.log('React WebSocket 연결 종료');
};
}
}
Expand Down Expand Up @@ -210,6 +214,8 @@ function App() {
<button onClick={handleCallClick}>전화 걸기</button>
</div>
</div>

<ToastContainer position="top-center" autoClose={2500} hideProgressBar={false} closeOnClick />
</div>
);
}
Expand Down

0 comments on commit ce2cc8e

Please sign in to comment.