Repository files navigation

CallBa

콜포비아 사용자를 위한 전화 발신 보조 웹 서비스

프로젝트 개요

전화 통화에 대한 불안으로 발신이 부담스러운 사용자를 위해
사용자 입력 기반의 통화 목적을 음성(TTS)으로 대신 전달하고,
실시간 자막 및 상황별 응답 추천으로 통화 전반을 지원합니다.

팀 & 역할

팀명: Legend
기간: 2025.09.04 – 2025.12.18

하서경김나림윤예진임은서
프로필
역할팀장팀원팀원팀원
담당Backend 개발
Twilio 통화 제어
STT/TTS 연동
Backend 개발
LLM 기반 통화 내용 요약
UI/Frontend 개발
DB 설계 및 연동
로그인/인증 구현
UI/Frontend 개발
발표 및 시연

기술 스택

Frontend

  • HTML, CSS, Vanilla JavaScript
  • Socket.IO Client

Backend

  • Node.js / Express
  • WebSocket (Twilio Media Stream)
  • Socket.IO

Voice / AI

  • Twilio (Call, TwiML, Media Stream)
  • Azure Speech-to-Text (Streaming)
  • Gemini (대화 추천 멘트, 통화 요약)
  • ElevenLabs (TTS)

Database / Infra

  • PostgreSQL
  • Prisma ORM
  • Docker / Docker Compose
  • ngrok

핵심 기능

전화 발신 및 통화 흐름 제어

  • 웹에서 발신 요청 시 Twilio Call API로 전화 연결
  • TwiML을 통해 통화 시작, 대기, 음성 재생 등 통화 흐름 제어
  • 통화 상태 콜백을 수신해 실시간 상태 동기화

실시간 음성 인식 및 자막 제공

  • Twilio Media Stream을 WebSocket으로 수신
  • μ-law 음성을 PCM으로 변환 후 Azure STT로 실시간 음성 인식
  • 인식 결과를 자막 형태로 프론트에 실시간 전달

AI 기반 대화 지원

  • STT 결과를 기반으로 Gemini가 상황별 추천 답변 생성
  • 추천 멘트를 프론트에 실시간 제공하여 전화 응대 보조

TTS 생성 및 재생

  • 추천 멘트 또는 사용자 입력 문장을 ElevenLabs TTS로 음성 생성
  • 생성된 음성을 통화 중 실시간 재생하거나 웹에서 미리듣기 지원

통화 요약 및 기록 관리

  • 통화 종료 시 전체 대화를 Gemini로 요약
  • 요약 및 대화 기록을 DB에 저장하고 조회 가능

실시간 음성 처리 아키텍처

  1. 웹에서 전화 발신 요청
  2. Twilio가 전화를 연결하고 Media Stream으로 음성 송출
  3. 서버가 음성을 WebSocket으로 수신 후 STT 수행
  4. 인식된 텍스트로 추천 멘트 생성 및 실시간 전달
  5. 선택된 멘트를 TTS로 생성해 통화 중 재생
  6. 통화 종료 후 요약 생성 및 저장

파이프라인 요약:
Twilio Media Stream → WebSocket → Azure STT → Gemini → Socket.IO → (ElevenLabs TTS)

ERD

프로젝트 구조

전체 디렉터리 구조
front/ # Node(Express) 서버 + 정적 프론트(바닐라 JS) 통합 디렉터리
├─ public/ # 프론트 정적 리소스(HTML/CSS/JS)
├─ src/ # 서버 핵심 로직
│ ├─ routes/ # HTTP API 라우트(auth/user/call/twilio)
│ ├─ sockets/ # 실시간 처리(WebSocket: Media Stream, Socket.IO)
│ ├─ services/ # 외부 연동 로직(Twilio, ElevenLabs, Gemini 등)
│ ├─ config/ # 환경변수 및 외부 클라이언트 초기화
│ ├─ auth/ # Google OAuth 설정
│ ├─ db/ # Prisma 클라이언트
│ └─ utils/ # 오디오 유틸(μ-law→PCM 변환 등)
├─ audio/ # TTS로 생성된 mp3 파일 정적 서빙
├─ prisma/ # Prisma 스키마/마이그레이션
├─ server.js # 서버 엔트리
├─ docker-compose.yml # 로컬 Postgres 실행
└─ .env.example # 환경변수 템플릿
디렉터리 구조 상세
front/
audio/ # TTS로 생성된 오디오(mp3) 정적 서빙 디렉터리
node_modules/ # 의존성
prisma/ # Prisma schema 및 migration 관리
public/ # 프론트 정적 리소스
css/ # 공통 스타일
javascript/ # 브라우저 스크립트
pages/ # HTML 페이지
src/ # 서버 핵심 로직
auth/
passport.js # Google OAuth 전략/세션 설정
config/
env.js # 환경변수 로딩, 외부 API 클라이언트 초기화
voice.js # 음성 프리셋 매핑/관리
db/
prisma.js # Prisma Client 초기화 및 DB 연결
routes/
authRoutes.js # 로그인, 콜백, 로그아웃 등
callRoutes.js # 통화 생성, 상태 관리, 미리듣기
twilioRoutes.js # TwiML 응답 및 Twilio 콜백
userRoutes.js # 프로필 조회, 보이스 설정
services/
ttsService.js # ElevenLabs TTS 호출, 오디오 파일 생성/저장
twilioService.js # Twilio Call 생성/업데이트/제어
summaryService.js # 통화 요약 (Gemini)
sockets/
mediaSocket.js # Twilio Media Stream WebSocket 수신 → STT(Azure) → 추천(Gemini) 파이프라인
frontendSocket.js # 프론트로 실시간 이벤트 Push(자막/추천멘트/통화 상태)
utils/
audio.js # 오디오 파일 경로/입출력 유틸
mulaw.js # 오디오 전처리: μ-law → PCM 변환
.env
.env.example # 환경변수 템플릿
.gitignore
docker-compose.yml # 로컬 Postgres 실행 설정
package-lock.json
prisma.config.ts
README.md
server.js # 서버 엔트리: Express 설정 + 라우트/소켓 초기화

실행 방법

1️⃣ 사전 준비

  • Node.js (권장: LTS)
  • Docker / Docker Compose
  • ngrok
  • 계정 및 API Key: Twilio, Azure STT, ElevenLabs, Gemini API

2️⃣ 환경변수 설정

프로젝트 루트(front/)에 .env 파일을 생성하고 아래 값을 설정합니다.
필수 값은 .env.example 파일을 참고하세요.

환경변수 목록 보기
# Server
PORT=3003
PUBLIC_HOST=
SERVER_URL=
# Twilio
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_API_KEY=
TWILIO_API_SECRET=
TWILIO_TWIML_APP_SID=
TWILIO_FROM_NUMBER=
TWILIO_CALLER_ID=
TWILIO_MEDIA_STREAM_URL= # (선택) 기본적으로 PUBLIC_HOST 기반 동작# STT / LLM
AZURE_SPEECH_KEY=
AZURE_SPEECH_REGION=
GEMINI_API_KEY=
# ElevenLabs
ELEVENLABS_API_KEY=
ELEVENLABS_MODEL_ID=
ELEVENLABS_VOICE_ID=
# ElevenLabs Voice Presets
ELEVENLABS_VOICE_ID_BRIGHT=
ELEVENLABS_VOICE_ID_CLEAR=
ELEVENLABS_VOICE_ID_CALM=
ELEVENLABS_VOICE_ID_WARM=
# Database
DATABASE_URL=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
POSTGRES_PORT=
POSTGRES_HOST=
# Auth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
SESSION_SECRET=

3️⃣ 서버 실행

npm install
docker-compose up -d # Postgres 실행
node server.js # 서버 실행

4️⃣ 외부 접근 설정 (ngrok)

Twilio Webhook 및 Media Stream 연결을 위해 ngrok으로 서버를 외부에 노출합니다. ngrok에서 발급된 URL을 PUBLIC_HOST 환경변수에 설정합니다.

ngrok http 3003

주요 엔드포인트

구분MethodPath설명
ClientPOST/calls전화 발신 요청 (시작 멘트 생성 후 Twilio 발신)
ClientGET/calls/history통화 기록 조회
ClientPOST/tts-previewTTS 미리듣기
TwilioPOST/call-status통화 상태 콜백
TwilioALL/twilio/answerTwiML 응답 (스트림 시작 + 시작 멘트 재생)
TwilioALL/twilio/holdTwiML 응답 (대기 유지 + 스트림 유지)

기술적 의사결정

Media Stream 기반 실시간 음성 처리

HTTP 업로드·폴링 방식 대신 Twilio Media Streams(WebSocket) 구조를 적용해,
통화 음성을 프레임 단위로 수신하며 STT 지연을 최소화했습니다.

  • HTTP 기반 업로드 방식은 음성 버퍼링 및 전송 지연 발생
  • WebSocket 스트리밍은 통화 흐름을 유지한 채 즉시 음성 처리 가능

구현 핵심

  • TwiML <Start><Stream />을 사용해 통화 오디오를 WebSocket으로 스트리밍
  • 서버에서 httpServer.upgrade를 통해 WebSocket 연결 수립
  • start / media / stop 이벤트 단위로 오디오 프레임 수신
  • μ-law 오디오를 PCM으로 변환 후 Azure STT 스트림 입력으로 전달

STT 결과 실시간 Push 구조

STT 인식 결과를 Socket 기반으로 즉시 프론트에 전달해 응답 지연을 최소화했습니다.
통화 중 실시간 자막 제공과 후속 응답 처리가 가능하도록 설계했습니다.

  • Polling 방식은 네트워크 오버헤드와 응답 지연 발생
  • Push 방식은 인식 완료 즉시 사용자에게 피드백 제공 가능

구현 핵심

  • Azure Speech SDK의 스트리밍 인식 방식 사용
  • STT 결과 수신 즉시 Socket 이벤트로 프론트엔드에 전달
  • 최근 인식 텍스트 + 시간 기준 중복 필터링 로직으로 반복 인식 방지

프론트엔드 연계

  • callSid 기준으로 Socket room 연결
  • STT 결과 수신 즉시 자막 표시
  • 인식 텍스트 기반 응답 추천 트리거 실행

서비스 단위 분리 설계 (Routes ↔ Services)

routes는 I/O 처리만 담당하고, services에 비즈니스 로직을 집중시켜 확장성과 유지보수성을 강화했습니다.
외부 벤더 의존성을 services 계층에 캡슐화해, STT·TTS·LLM 교체 시 코드 변경 범위를 최소화하도록 설계했습니다.

설계 원칙

  • routes
    • HTTP / Socket 이벤트 수신
    • 요청·응답 처리만 담당
  • services
    • 핵심 비즈니스 로직
    • 외부 API(STT, TTS, LLM, Twilio) 호출 담당
  • 벤더 교체 및 기능 확장 시 services 계층만 수정하도록 구조화

핵심 구현 예시

  • TTS
    • src/services/ttsService.js
    • synthesizeToFile()
  • 통화 제어
    • src/services/twilioService.js
    • playToCall()
  • 요약 처리
    • src/services/summaryService.js
    • summarizeCall()

트러블슈팅

OAuth 로그인 후 로그인 전 화면으로 되돌아가는 이슈

  • 원인: 서버는 세션 기반 인증(Passport)을 사용했으나, 프론트는 localStorage userId 기준으로 로그인 상태를 판단해 인증 기준이 불일치
  • 해결: 로그인 성공 후 /api/me를 통해 세션 사용자 정보를 조회하고, 프론트 상태를 세션 기준으로 재동기화하여 단일 인증 흐름으로 통합

사용자별 보이스 설정 구조를 단일 결정 흐름으로 개선

  • 원인: User 테이블에 보이스 정보를 포함할 경우 우선순위 관리가 어려움.
  • 해결: 보이스 설정을 UserVoiceSetting으로 분리하고, 프리셋 → 사용자 설정(DB) → 기본값 순으로 결정 로직을 명시

실시간 WebSocket 연결 끊김 및 재연결 이슈

  • 원인: 프론트–서버 WebSocket 연결이 끊길 경우, 소켓에 바인딩된 callSid / userId 컨텍스트가 유실되어 통화 중 이벤트(TTS 재생, 추천 멘트)가 정상 처리되지 않음
  • 해결: 소켓 연결 시 bind.call 이벤트로 통화 컨텍스트를 명시적으로 바인딩하고, 이벤트 처리 시 바인딩 여부를 검증하여 재연결 후에도 동일한 통화 단위(callSid) 기준으로 상태를 재동기화하도록 설계

Twilio 콜백 기반 통화 상태 불일치 이슈

  • 원인: 통화 종료/실패 이벤트가 Twilio 콜백, 사용자 종료 요청, WebSocket 이벤트 등 여러 경로로 분산되어 통화 상태 관리가 일관되지 않음
  • 해결: 통화 상태 변경을 Twilio statusCallback과 서버 콜백(/call-status)을 중심으로 단일화하고, 통화 종료 시 Socket Room(callSid) 기준으로 이벤트를 전파하여 클라이언트 상태를 일관되게 동기화

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

CallBa

콜포비아 사용자를 위한 전화 발신 보조 웹 서비스

프로젝트 개요

전화 통화에 대한 불안으로 발신이 부담스러운 사용자를 위해
사용자 입력 기반의 통화 목적을 음성(TTS)으로 대신 전달하고,
실시간 자막 및 상황별 응답 추천으로 통화 전반을 지원합니다.

팀 & 역할

팀명: Legend
기간: 2025.09.04 – 2025.12.18

하서경김나림윤예진임은서
프로필
역할팀장팀원팀원팀원
담당Backend 개발
Twilio 통화 제어
STT/TTS 연동
Backend 개발
LLM 기반 통화 내용 요약
UI/Frontend 개발
DB 설계 및 연동
로그인/인증 구현
UI/Frontend 개발
발표 및 시연

기술 스택

Frontend

  • HTML, CSS, Vanilla JavaScript
  • Socket.IO Client

Backend

  • Node.js / Express
  • WebSocket (Twilio Media Stream)
  • Socket.IO

Voice / AI

  • Twilio (Call, TwiML, Media Stream)
  • Azure Speech-to-Text (Streaming)
  • Gemini (대화 추천 멘트, 통화 요약)
  • ElevenLabs (TTS)

Database / Infra

  • PostgreSQL
  • Prisma ORM
  • Docker / Docker Compose
  • ngrok

핵심 기능

전화 발신 및 통화 흐름 제어

  • 웹에서 발신 요청 시 Twilio Call API로 전화 연결
  • TwiML을 통해 통화 시작, 대기, 음성 재생 등 통화 흐름 제어
  • 통화 상태 콜백을 수신해 실시간 상태 동기화

실시간 음성 인식 및 자막 제공

  • Twilio Media Stream을 WebSocket으로 수신
  • μ-law 음성을 PCM으로 변환 후 Azure STT로 실시간 음성 인식
  • 인식 결과를 자막 형태로 프론트에 실시간 전달

AI 기반 대화 지원

  • STT 결과를 기반으로 Gemini가 상황별 추천 답변 생성
  • 추천 멘트를 프론트에 실시간 제공하여 전화 응대 보조

TTS 생성 및 재생

  • 추천 멘트 또는 사용자 입력 문장을 ElevenLabs TTS로 음성 생성
  • 생성된 음성을 통화 중 실시간 재생하거나 웹에서 미리듣기 지원

통화 요약 및 기록 관리

  • 통화 종료 시 전체 대화를 Gemini로 요약
  • 요약 및 대화 기록을 DB에 저장하고 조회 가능

실시간 음성 처리 아키텍처

  1. 웹에서 전화 발신 요청
  2. Twilio가 전화를 연결하고 Media Stream으로 음성 송출
  3. 서버가 음성을 WebSocket으로 수신 후 STT 수행
  4. 인식된 텍스트로 추천 멘트 생성 및 실시간 전달
  5. 선택된 멘트를 TTS로 생성해 통화 중 재생
  6. 통화 종료 후 요약 생성 및 저장

파이프라인 요약:
Twilio Media Stream → WebSocket → Azure STT → Gemini → Socket.IO → (ElevenLabs TTS)

ERD

프로젝트 구조

전체 디렉터리 구조
front/ # Node(Express) 서버 + 정적 프론트(바닐라 JS) 통합 디렉터리
├─ public/ # 프론트 정적 리소스(HTML/CSS/JS)
├─ src/ # 서버 핵심 로직
│ ├─ routes/ # HTTP API 라우트(auth/user/call/twilio)
│ ├─ sockets/ # 실시간 처리(WebSocket: Media Stream, Socket.IO)
│ ├─ services/ # 외부 연동 로직(Twilio, ElevenLabs, Gemini 등)
│ ├─ config/ # 환경변수 및 외부 클라이언트 초기화
│ ├─ auth/ # Google OAuth 설정
│ ├─ db/ # Prisma 클라이언트
│ └─ utils/ # 오디오 유틸(μ-law→PCM 변환 등)
├─ audio/ # TTS로 생성된 mp3 파일 정적 서빙
├─ prisma/ # Prisma 스키마/마이그레이션
├─ server.js # 서버 엔트리
├─ docker-compose.yml # 로컬 Postgres 실행
└─ .env.example # 환경변수 템플릿
디렉터리 구조 상세
front/
audio/ # TTS로 생성된 오디오(mp3) 정적 서빙 디렉터리
node_modules/ # 의존성
prisma/ # Prisma schema 및 migration 관리
public/ # 프론트 정적 리소스
css/ # 공통 스타일
javascript/ # 브라우저 스크립트
pages/ # HTML 페이지
src/ # 서버 핵심 로직
auth/
passport.js # Google OAuth 전략/세션 설정
config/
env.js # 환경변수 로딩, 외부 API 클라이언트 초기화
voice.js # 음성 프리셋 매핑/관리
db/
prisma.js # Prisma Client 초기화 및 DB 연결
routes/
authRoutes.js # 로그인, 콜백, 로그아웃 등
callRoutes.js # 통화 생성, 상태 관리, 미리듣기
twilioRoutes.js # TwiML 응답 및 Twilio 콜백
userRoutes.js # 프로필 조회, 보이스 설정
services/
ttsService.js # ElevenLabs TTS 호출, 오디오 파일 생성/저장
twilioService.js # Twilio Call 생성/업데이트/제어
summaryService.js # 통화 요약 (Gemini)
sockets/
mediaSocket.js # Twilio Media Stream WebSocket 수신 → STT(Azure) → 추천(Gemini) 파이프라인
frontendSocket.js # 프론트로 실시간 이벤트 Push(자막/추천멘트/통화 상태)
utils/
audio.js # 오디오 파일 경로/입출력 유틸
mulaw.js # 오디오 전처리: μ-law → PCM 변환
.env
.env.example # 환경변수 템플릿
.gitignore
docker-compose.yml # 로컬 Postgres 실행 설정
package-lock.json
prisma.config.ts
README.md
server.js # 서버 엔트리: Express 설정 + 라우트/소켓 초기화

실행 방법

1️⃣ 사전 준비

  • Node.js (권장: LTS)
  • Docker / Docker Compose
  • ngrok
  • 계정 및 API Key: Twilio, Azure STT, ElevenLabs, Gemini API

2️⃣ 환경변수 설정

프로젝트 루트(front/)에 .env 파일을 생성하고 아래 값을 설정합니다.
필수 값은 .env.example 파일을 참고하세요.

환경변수 목록 보기
# Server
PORT=3003
PUBLIC_HOST=
SERVER_URL=
# Twilio
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_API_KEY=
TWILIO_API_SECRET=
TWILIO_TWIML_APP_SID=
TWILIO_FROM_NUMBER=
TWILIO_CALLER_ID=
TWILIO_MEDIA_STREAM_URL= # (선택) 기본적으로 PUBLIC_HOST 기반 동작# STT / LLM
AZURE_SPEECH_KEY=
AZURE_SPEECH_REGION=
GEMINI_API_KEY=
# ElevenLabs
ELEVENLABS_API_KEY=
ELEVENLABS_MODEL_ID=
ELEVENLABS_VOICE_ID=
# ElevenLabs Voice Presets
ELEVENLABS_VOICE_ID_BRIGHT=
ELEVENLABS_VOICE_ID_CLEAR=
ELEVENLABS_VOICE_ID_CALM=
ELEVENLABS_VOICE_ID_WARM=
# Database
DATABASE_URL=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
POSTGRES_PORT=
POSTGRES_HOST=
# Auth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
SESSION_SECRET=

3️⃣ 서버 실행

npm install
docker-compose up -d # Postgres 실행
node server.js # 서버 실행

4️⃣ 외부 접근 설정 (ngrok)

Twilio Webhook 및 Media Stream 연결을 위해 ngrok으로 서버를 외부에 노출합니다. ngrok에서 발급된 URL을 PUBLIC_HOST 환경변수에 설정합니다.

ngrok http 3003

주요 엔드포인트

구분MethodPath설명
ClientPOST/calls전화 발신 요청 (시작 멘트 생성 후 Twilio 발신)
ClientGET/calls/history통화 기록 조회
ClientPOST/tts-previewTTS 미리듣기
TwilioPOST/call-status통화 상태 콜백
TwilioALL/twilio/answerTwiML 응답 (스트림 시작 + 시작 멘트 재생)
TwilioALL/twilio/holdTwiML 응답 (대기 유지 + 스트림 유지)

기술적 의사결정

Media Stream 기반 실시간 음성 처리

HTTP 업로드·폴링 방식 대신 Twilio Media Streams(WebSocket) 구조를 적용해,
통화 음성을 프레임 단위로 수신하며 STT 지연을 최소화했습니다.

  • HTTP 기반 업로드 방식은 음성 버퍼링 및 전송 지연 발생
  • WebSocket 스트리밍은 통화 흐름을 유지한 채 즉시 음성 처리 가능

구현 핵심

  • TwiML <Start><Stream />을 사용해 통화 오디오를 WebSocket으로 스트리밍
  • 서버에서 httpServer.upgrade를 통해 WebSocket 연결 수립
  • start / media / stop 이벤트 단위로 오디오 프레임 수신
  • μ-law 오디오를 PCM으로 변환 후 Azure STT 스트림 입력으로 전달

STT 결과 실시간 Push 구조

STT 인식 결과를 Socket 기반으로 즉시 프론트에 전달해 응답 지연을 최소화했습니다.
통화 중 실시간 자막 제공과 후속 응답 처리가 가능하도록 설계했습니다.

  • Polling 방식은 네트워크 오버헤드와 응답 지연 발생
  • Push 방식은 인식 완료 즉시 사용자에게 피드백 제공 가능

구현 핵심

  • Azure Speech SDK의 스트리밍 인식 방식 사용
  • STT 결과 수신 즉시 Socket 이벤트로 프론트엔드에 전달
  • 최근 인식 텍스트 + 시간 기준 중복 필터링 로직으로 반복 인식 방지

프론트엔드 연계

  • callSid 기준으로 Socket room 연결
  • STT 결과 수신 즉시 자막 표시
  • 인식 텍스트 기반 응답 추천 트리거 실행

서비스 단위 분리 설계 (Routes ↔ Services)

routes는 I/O 처리만 담당하고, services에 비즈니스 로직을 집중시켜 확장성과 유지보수성을 강화했습니다.
외부 벤더 의존성을 services 계층에 캡슐화해, STT·TTS·LLM 교체 시 코드 변경 범위를 최소화하도록 설계했습니다.

설계 원칙

  • routes
    • HTTP / Socket 이벤트 수신
    • 요청·응답 처리만 담당
  • services
    • 핵심 비즈니스 로직
    • 외부 API(STT, TTS, LLM, Twilio) 호출 담당
  • 벤더 교체 및 기능 확장 시 services 계층만 수정하도록 구조화

핵심 구현 예시

  • TTS
    • src/services/ttsService.js
    • synthesizeToFile()
  • 통화 제어
    • src/services/twilioService.js
    • playToCall()
  • 요약 처리
    • src/services/summaryService.js
    • summarizeCall()

트러블슈팅

OAuth 로그인 후 로그인 전 화면으로 되돌아가는 이슈

  • 원인: 서버는 세션 기반 인증(Passport)을 사용했으나, 프론트는 localStorage userId 기준으로 로그인 상태를 판단해 인증 기준이 불일치
  • 해결: 로그인 성공 후 /api/me를 통해 세션 사용자 정보를 조회하고, 프론트 상태를 세션 기준으로 재동기화하여 단일 인증 흐름으로 통합

사용자별 보이스 설정 구조를 단일 결정 흐름으로 개선

  • 원인: User 테이블에 보이스 정보를 포함할 경우 우선순위 관리가 어려움.
  • 해결: 보이스 설정을 UserVoiceSetting으로 분리하고, 프리셋 → 사용자 설정(DB) → 기본값 순으로 결정 로직을 명시

실시간 WebSocket 연결 끊김 및 재연결 이슈

  • 원인: 프론트–서버 WebSocket 연결이 끊길 경우, 소켓에 바인딩된 callSid / userId 컨텍스트가 유실되어 통화 중 이벤트(TTS 재생, 추천 멘트)가 정상 처리되지 않음
  • 해결: 소켓 연결 시 bind.call 이벤트로 통화 컨텍스트를 명시적으로 바인딩하고, 이벤트 처리 시 바인딩 여부를 검증하여 재연결 후에도 동일한 통화 단위(callSid) 기준으로 상태를 재동기화하도록 설계

Twilio 콜백 기반 통화 상태 불일치 이슈

  • 원인: 통화 종료/실패 이벤트가 Twilio 콜백, 사용자 종료 요청, WebSocket 이벤트 등 여러 경로로 분산되어 통화 상태 관리가 일관되지 않음
  • 해결: 통화 상태 변경을 Twilio statusCallback과 서버 콜백(/call-status)을 중심으로 단일화하고, 통화 종료 시 Socket Room(callSid) 기준으로 이벤트를 전파하여 클라이언트 상태를 일관되게 동기화

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CallBa

콜포비아 사용자를 위한 전화 발신 보조 웹 서비스

프로젝트 개요

전화 통화에 대한 불안으로 발신이 부담스러운 사용자를 위해
사용자 입력 기반의 통화 목적을 음성(TTS)으로 대신 전달하고,
실시간 자막 및 상황별 응답 추천으로 통화 전반을 지원합니다.

팀 & 역할

팀명: Legend
기간: 2025.09.04 – 2025.12.18

하서경김나림윤예진임은서
프로필
역할팀장팀원팀원팀원
담당Backend 개발
Twilio 통화 제어
STT/TTS 연동
Backend 개발
LLM 기반 통화 내용 요약
UI/Frontend 개발
DB 설계 및 연동
로그인/인증 구현
UI/Frontend 개발
발표 및 시연

기술 스택

Frontend

  • HTML, CSS, Vanilla JavaScript
  • Socket.IO Client

Backend

  • Node.js / Express
  • WebSocket (Twilio Media Stream)
  • Socket.IO

Voice / AI

  • Twilio (Call, TwiML, Media Stream)
  • Azure Speech-to-Text (Streaming)
  • Gemini (대화 추천 멘트, 통화 요약)
  • ElevenLabs (TTS)

Database / Infra

  • PostgreSQL
  • Prisma ORM
  • Docker / Docker Compose
  • ngrok

핵심 기능

전화 발신 및 통화 흐름 제어

  • 웹에서 발신 요청 시 Twilio Call API로 전화 연결
  • TwiML을 통해 통화 시작, 대기, 음성 재생 등 통화 흐름 제어
  • 통화 상태 콜백을 수신해 실시간 상태 동기화

실시간 음성 인식 및 자막 제공

  • Twilio Media Stream을 WebSocket으로 수신
  • μ-law 음성을 PCM으로 변환 후 Azure STT로 실시간 음성 인식
  • 인식 결과를 자막 형태로 프론트에 실시간 전달

AI 기반 대화 지원

  • STT 결과를 기반으로 Gemini가 상황별 추천 답변 생성
  • 추천 멘트를 프론트에 실시간 제공하여 전화 응대 보조

TTS 생성 및 재생

  • 추천 멘트 또는 사용자 입력 문장을 ElevenLabs TTS로 음성 생성
  • 생성된 음성을 통화 중 실시간 재생하거나 웹에서 미리듣기 지원

통화 요약 및 기록 관리

  • 통화 종료 시 전체 대화를 Gemini로 요약
  • 요약 및 대화 기록을 DB에 저장하고 조회 가능

실시간 음성 처리 아키텍처

  1. 웹에서 전화 발신 요청
  2. Twilio가 전화를 연결하고 Media Stream으로 음성 송출
  3. 서버가 음성을 WebSocket으로 수신 후 STT 수행
  4. 인식된 텍스트로 추천 멘트 생성 및 실시간 전달
  5. 선택된 멘트를 TTS로 생성해 통화 중 재생
  6. 통화 종료 후 요약 생성 및 저장

파이프라인 요약:
Twilio Media Stream → WebSocket → Azure STT → Gemini → Socket.IO → (ElevenLabs TTS)

ERD

프로젝트 구조

전체 디렉터리 구조
front/ # Node(Express) 서버 + 정적 프론트(바닐라 JS) 통합 디렉터리
├─ public/ # 프론트 정적 리소스(HTML/CSS/JS)
├─ src/ # 서버 핵심 로직
│ ├─ routes/ # HTTP API 라우트(auth/user/call/twilio)
│ ├─ sockets/ # 실시간 처리(WebSocket: Media Stream, Socket.IO)
│ ├─ services/ # 외부 연동 로직(Twilio, ElevenLabs, Gemini 등)
│ ├─ config/ # 환경변수 및 외부 클라이언트 초기화
│ ├─ auth/ # Google OAuth 설정
│ ├─ db/ # Prisma 클라이언트
│ └─ utils/ # 오디오 유틸(μ-law→PCM 변환 등)
├─ audio/ # TTS로 생성된 mp3 파일 정적 서빙
├─ prisma/ # Prisma 스키마/마이그레이션
├─ server.js # 서버 엔트리
├─ docker-compose.yml # 로컬 Postgres 실행
└─ .env.example # 환경변수 템플릿
디렉터리 구조 상세
front/
audio/ # TTS로 생성된 오디오(mp3) 정적 서빙 디렉터리
node_modules/ # 의존성
prisma/ # Prisma schema 및 migration 관리
public/ # 프론트 정적 리소스
css/ # 공통 스타일
javascript/ # 브라우저 스크립트
pages/ # HTML 페이지
src/ # 서버 핵심 로직
auth/
passport.js # Google OAuth 전략/세션 설정
config/
env.js # 환경변수 로딩, 외부 API 클라이언트 초기화
voice.js # 음성 프리셋 매핑/관리
db/
prisma.js # Prisma Client 초기화 및 DB 연결
routes/
authRoutes.js # 로그인, 콜백, 로그아웃 등
callRoutes.js # 통화 생성, 상태 관리, 미리듣기
twilioRoutes.js # TwiML 응답 및 Twilio 콜백
userRoutes.js # 프로필 조회, 보이스 설정
services/
ttsService.js # ElevenLabs TTS 호출, 오디오 파일 생성/저장
twilioService.js # Twilio Call 생성/업데이트/제어
summaryService.js # 통화 요약 (Gemini)
sockets/
mediaSocket.js # Twilio Media Stream WebSocket 수신 → STT(Azure) → 추천(Gemini) 파이프라인
frontendSocket.js # 프론트로 실시간 이벤트 Push(자막/추천멘트/통화 상태)
utils/
audio.js # 오디오 파일 경로/입출력 유틸
mulaw.js # 오디오 전처리: μ-law → PCM 변환
.env
.env.example # 환경변수 템플릿
.gitignore
docker-compose.yml # 로컬 Postgres 실행 설정
package-lock.json
prisma.config.ts
README.md
server.js # 서버 엔트리: Express 설정 + 라우트/소켓 초기화

실행 방법

1️⃣ 사전 준비

  • Node.js (권장: LTS)
  • Docker / Docker Compose
  • ngrok
  • 계정 및 API Key: Twilio, Azure STT, ElevenLabs, Gemini API

2️⃣ 환경변수 설정

프로젝트 루트(front/)에 .env 파일을 생성하고 아래 값을 설정합니다.
필수 값은 .env.example 파일을 참고하세요.

환경변수 목록 보기
# Server
PORT=3003
PUBLIC_HOST=
SERVER_URL=
# Twilio
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_API_KEY=
TWILIO_API_SECRET=
TWILIO_TWIML_APP_SID=
TWILIO_FROM_NUMBER=
TWILIO_CALLER_ID=
TWILIO_MEDIA_STREAM_URL= # (선택) 기본적으로 PUBLIC_HOST 기반 동작# STT / LLM
AZURE_SPEECH_KEY=
AZURE_SPEECH_REGION=
GEMINI_API_KEY=
# ElevenLabs
ELEVENLABS_API_KEY=
ELEVENLABS_MODEL_ID=
ELEVENLABS_VOICE_ID=
# ElevenLabs Voice Presets
ELEVENLABS_VOICE_ID_BRIGHT=
ELEVENLABS_VOICE_ID_CLEAR=
ELEVENLABS_VOICE_ID_CALM=
ELEVENLABS_VOICE_ID_WARM=
# Database
DATABASE_URL=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
POSTGRES_PORT=
POSTGRES_HOST=
# Auth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
SESSION_SECRET=

3️⃣ 서버 실행

npm install
docker-compose up -d # Postgres 실행
node server.js # 서버 실행

4️⃣ 외부 접근 설정 (ngrok)

Twilio Webhook 및 Media Stream 연결을 위해 ngrok으로 서버를 외부에 노출합니다. ngrok에서 발급된 URL을 PUBLIC_HOST 환경변수에 설정합니다.

ngrok http 3003

주요 엔드포인트

구분MethodPath설명
ClientPOST/calls전화 발신 요청 (시작 멘트 생성 후 Twilio 발신)
ClientGET/calls/history통화 기록 조회
ClientPOST/tts-previewTTS 미리듣기
TwilioPOST/call-status통화 상태 콜백
TwilioALL/twilio/answerTwiML 응답 (스트림 시작 + 시작 멘트 재생)
TwilioALL/twilio/holdTwiML 응답 (대기 유지 + 스트림 유지)

기술적 의사결정

Media Stream 기반 실시간 음성 처리

HTTP 업로드·폴링 방식 대신 Twilio Media Streams(WebSocket) 구조를 적용해,
통화 음성을 프레임 단위로 수신하며 STT 지연을 최소화했습니다.

  • HTTP 기반 업로드 방식은 음성 버퍼링 및 전송 지연 발생
  • WebSocket 스트리밍은 통화 흐름을 유지한 채 즉시 음성 처리 가능

구현 핵심

  • TwiML <Start><Stream />을 사용해 통화 오디오를 WebSocket으로 스트리밍
  • 서버에서 httpServer.upgrade를 통해 WebSocket 연결 수립
  • start / media / stop 이벤트 단위로 오디오 프레임 수신
  • μ-law 오디오를 PCM으로 변환 후 Azure STT 스트림 입력으로 전달

STT 결과 실시간 Push 구조

STT 인식 결과를 Socket 기반으로 즉시 프론트에 전달해 응답 지연을 최소화했습니다.
통화 중 실시간 자막 제공과 후속 응답 처리가 가능하도록 설계했습니다.

  • Polling 방식은 네트워크 오버헤드와 응답 지연 발생
  • Push 방식은 인식 완료 즉시 사용자에게 피드백 제공 가능

구현 핵심

  • Azure Speech SDK의 스트리밍 인식 방식 사용
  • STT 결과 수신 즉시 Socket 이벤트로 프론트엔드에 전달
  • 최근 인식 텍스트 + 시간 기준 중복 필터링 로직으로 반복 인식 방지

프론트엔드 연계

  • callSid 기준으로 Socket room 연결
  • STT 결과 수신 즉시 자막 표시
  • 인식 텍스트 기반 응답 추천 트리거 실행

서비스 단위 분리 설계 (Routes ↔ Services)

routes는 I/O 처리만 담당하고, services에 비즈니스 로직을 집중시켜 확장성과 유지보수성을 강화했습니다.
외부 벤더 의존성을 services 계층에 캡슐화해, STT·TTS·LLM 교체 시 코드 변경 범위를 최소화하도록 설계했습니다.

설계 원칙

  • routes
    • HTTP / Socket 이벤트 수신
    • 요청·응답 처리만 담당
  • services
    • 핵심 비즈니스 로직
    • 외부 API(STT, TTS, LLM, Twilio) 호출 담당
  • 벤더 교체 및 기능 확장 시 services 계층만 수정하도록 구조화

핵심 구현 예시

  • TTS
    • src/services/ttsService.js
    • synthesizeToFile()
  • 통화 제어
    • src/services/twilioService.js
    • playToCall()
  • 요약 처리
    • src/services/summaryService.js
    • summarizeCall()

트러블슈팅

OAuth 로그인 후 로그인 전 화면으로 되돌아가는 이슈

  • 원인: 서버는 세션 기반 인증(Passport)을 사용했으나, 프론트는 localStorage userId 기준으로 로그인 상태를 판단해 인증 기준이 불일치
  • 해결: 로그인 성공 후 /api/me를 통해 세션 사용자 정보를 조회하고, 프론트 상태를 세션 기준으로 재동기화하여 단일 인증 흐름으로 통합

사용자별 보이스 설정 구조를 단일 결정 흐름으로 개선

  • 원인: User 테이블에 보이스 정보를 포함할 경우 우선순위 관리가 어려움.
  • 해결: 보이스 설정을 UserVoiceSetting으로 분리하고, 프리셋 → 사용자 설정(DB) → 기본값 순으로 결정 로직을 명시

실시간 WebSocket 연결 끊김 및 재연결 이슈

  • 원인: 프론트–서버 WebSocket 연결이 끊길 경우, 소켓에 바인딩된 callSid / userId 컨텍스트가 유실되어 통화 중 이벤트(TTS 재생, 추천 멘트)가 정상 처리되지 않음
  • 해결: 소켓 연결 시 bind.call 이벤트로 통화 컨텍스트를 명시적으로 바인딩하고, 이벤트 처리 시 바인딩 여부를 검증하여 재연결 후에도 동일한 통화 단위(callSid) 기준으로 상태를 재동기화하도록 설계

Twilio 콜백 기반 통화 상태 불일치 이슈

  • 원인: 통화 종료/실패 이벤트가 Twilio 콜백, 사용자 종료 요청, WebSocket 이벤트 등 여러 경로로 분산되어 통화 상태 관리가 일관되지 않음
  • 해결: 통화 상태 변경을 Twilio statusCallback과 서버 콜백(/call-status)을 중심으로 단일화하고, 통화 종료 시 Socket Room(callSid) 기준으로 이벤트를 전파하여 클라이언트 상태를 일관되게 동기화

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CallBa

콜포비아 사용자를 위한 전화 발신 보조 웹 서비스

프로젝트 개요

전화 통화에 대한 불안으로 발신이 부담스러운 사용자를 위해
사용자 입력 기반의 통화 목적을 음성(TTS)으로 대신 전달하고,
실시간 자막 및 상황별 응답 추천으로 통화 전반을 지원합니다.

팀 & 역할

팀명: Legend
기간: 2025.09.04 – 2025.12.18

하서경김나림윤예진임은서
프로필
역할팀장팀원팀원팀원
담당Backend 개발
Twilio 통화 제어
STT/TTS 연동
Backend 개발
LLM 기반 통화 내용 요약
UI/Frontend 개발
DB 설계 및 연동
로그인/인증 구현
UI/Frontend 개발
발표 및 시연

기술 스택

Frontend

  • HTML, CSS, Vanilla JavaScript
  • Socket.IO Client

Backend

  • Node.js / Express
  • WebSocket (Twilio Media Stream)
  • Socket.IO

Voice / AI

  • Twilio (Call, TwiML, Media Stream)
  • Azure Speech-to-Text (Streaming)
  • Gemini (대화 추천 멘트, 통화 요약)
  • ElevenLabs (TTS)

Database / Infra

  • PostgreSQL
  • Prisma ORM
  • Docker / Docker Compose
  • ngrok

핵심 기능

전화 발신 및 통화 흐름 제어

  • 웹에서 발신 요청 시 Twilio Call API로 전화 연결
  • TwiML을 통해 통화 시작, 대기, 음성 재생 등 통화 흐름 제어
  • 통화 상태 콜백을 수신해 실시간 상태 동기화

실시간 음성 인식 및 자막 제공

  • Twilio Media Stream을 WebSocket으로 수신
  • μ-law 음성을 PCM으로 변환 후 Azure STT로 실시간 음성 인식
  • 인식 결과를 자막 형태로 프론트에 실시간 전달

AI 기반 대화 지원

  • STT 결과를 기반으로 Gemini가 상황별 추천 답변 생성
  • 추천 멘트를 프론트에 실시간 제공하여 전화 응대 보조

TTS 생성 및 재생

  • 추천 멘트 또는 사용자 입력 문장을 ElevenLabs TTS로 음성 생성
  • 생성된 음성을 통화 중 실시간 재생하거나 웹에서 미리듣기 지원

통화 요약 및 기록 관리

  • 통화 종료 시 전체 대화를 Gemini로 요약
  • 요약 및 대화 기록을 DB에 저장하고 조회 가능

실시간 음성 처리 아키텍처

  1. 웹에서 전화 발신 요청
  2. Twilio가 전화를 연결하고 Media Stream으로 음성 송출
  3. 서버가 음성을 WebSocket으로 수신 후 STT 수행
  4. 인식된 텍스트로 추천 멘트 생성 및 실시간 전달
  5. 선택된 멘트를 TTS로 생성해 통화 중 재생
  6. 통화 종료 후 요약 생성 및 저장

파이프라인 요약:
Twilio Media Stream → WebSocket → Azure STT → Gemini → Socket.IO → (ElevenLabs TTS)

ERD

프로젝트 구조

전체 디렉터리 구조
front/ # Node(Express) 서버 + 정적 프론트(바닐라 JS) 통합 디렉터리
├─ public/ # 프론트 정적 리소스(HTML/CSS/JS)
├─ src/ # 서버 핵심 로직
│ ├─ routes/ # HTTP API 라우트(auth/user/call/twilio)
│ ├─ sockets/ # 실시간 처리(WebSocket: Media Stream, Socket.IO)
│ ├─ services/ # 외부 연동 로직(Twilio, ElevenLabs, Gemini 등)
│ ├─ config/ # 환경변수 및 외부 클라이언트 초기화
│ ├─ auth/ # Google OAuth 설정
│ ├─ db/ # Prisma 클라이언트
│ └─ utils/ # 오디오 유틸(μ-law→PCM 변환 등)
├─ audio/ # TTS로 생성된 mp3 파일 정적 서빙
├─ prisma/ # Prisma 스키마/마이그레이션
├─ server.js # 서버 엔트리
├─ docker-compose.yml # 로컬 Postgres 실행
└─ .env.example # 환경변수 템플릿
디렉터리 구조 상세
front/
audio/ # TTS로 생성된 오디오(mp3) 정적 서빙 디렉터리
node_modules/ # 의존성
prisma/ # Prisma schema 및 migration 관리
public/ # 프론트 정적 리소스
css/ # 공통 스타일
javascript/ # 브라우저 스크립트
pages/ # HTML 페이지
src/ # 서버 핵심 로직
auth/
passport.js # Google OAuth 전략/세션 설정
config/
env.js # 환경변수 로딩, 외부 API 클라이언트 초기화
voice.js # 음성 프리셋 매핑/관리
db/
prisma.js # Prisma Client 초기화 및 DB 연결
routes/
authRoutes.js # 로그인, 콜백, 로그아웃 등
callRoutes.js # 통화 생성, 상태 관리, 미리듣기
twilioRoutes.js # TwiML 응답 및 Twilio 콜백
userRoutes.js # 프로필 조회, 보이스 설정
services/
ttsService.js # ElevenLabs TTS 호출, 오디오 파일 생성/저장
twilioService.js # Twilio Call 생성/업데이트/제어
summaryService.js # 통화 요약 (Gemini)
sockets/
mediaSocket.js # Twilio Media Stream WebSocket 수신 → STT(Azure) → 추천(Gemini) 파이프라인
frontendSocket.js # 프론트로 실시간 이벤트 Push(자막/추천멘트/통화 상태)
utils/
audio.js # 오디오 파일 경로/입출력 유틸
mulaw.js # 오디오 전처리: μ-law → PCM 변환
.env
.env.example # 환경변수 템플릿
.gitignore
docker-compose.yml # 로컬 Postgres 실행 설정
package-lock.json
prisma.config.ts
README.md
server.js # 서버 엔트리: Express 설정 + 라우트/소켓 초기화

실행 방법

1️⃣ 사전 준비

  • Node.js (권장: LTS)
  • Docker / Docker Compose
  • ngrok
  • 계정 및 API Key: Twilio, Azure STT, ElevenLabs, Gemini API

2️⃣ 환경변수 설정

프로젝트 루트(front/)에 .env 파일을 생성하고 아래 값을 설정합니다.
필수 값은 .env.example 파일을 참고하세요.

환경변수 목록 보기
# Server
PORT=3003
PUBLIC_HOST=
SERVER_URL=
# Twilio
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_API_KEY=
TWILIO_API_SECRET=
TWILIO_TWIML_APP_SID=
TWILIO_FROM_NUMBER=
TWILIO_CALLER_ID=
TWILIO_MEDIA_STREAM_URL= # (선택) 기본적으로 PUBLIC_HOST 기반 동작# STT / LLM
AZURE_SPEECH_KEY=
AZURE_SPEECH_REGION=
GEMINI_API_KEY=
# ElevenLabs
ELEVENLABS_API_KEY=
ELEVENLABS_MODEL_ID=
ELEVENLABS_VOICE_ID=
# ElevenLabs Voice Presets
ELEVENLABS_VOICE_ID_BRIGHT=
ELEVENLABS_VOICE_ID_CLEAR=
ELEVENLABS_VOICE_ID_CALM=
ELEVENLABS_VOICE_ID_WARM=
# Database
DATABASE_URL=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
POSTGRES_PORT=
POSTGRES_HOST=
# Auth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
SESSION_SECRET=

3️⃣ 서버 실행

npm install
docker-compose up -d # Postgres 실행
node server.js # 서버 실행

4️⃣ 외부 접근 설정 (ngrok)

Twilio Webhook 및 Media Stream 연결을 위해 ngrok으로 서버를 외부에 노출합니다. ngrok에서 발급된 URL을 PUBLIC_HOST 환경변수에 설정합니다.

ngrok http 3003

주요 엔드포인트

구분MethodPath설명
ClientPOST/calls전화 발신 요청 (시작 멘트 생성 후 Twilio 발신)
ClientGET/calls/history통화 기록 조회
ClientPOST/tts-previewTTS 미리듣기
TwilioPOST/call-status통화 상태 콜백
TwilioALL/twilio/answerTwiML 응답 (스트림 시작 + 시작 멘트 재생)
TwilioALL/twilio/holdTwiML 응답 (대기 유지 + 스트림 유지)

기술적 의사결정

Media Stream 기반 실시간 음성 처리

HTTP 업로드·폴링 방식 대신 Twilio Media Streams(WebSocket) 구조를 적용해,
통화 음성을 프레임 단위로 수신하며 STT 지연을 최소화했습니다.

  • HTTP 기반 업로드 방식은 음성 버퍼링 및 전송 지연 발생
  • WebSocket 스트리밍은 통화 흐름을 유지한 채 즉시 음성 처리 가능

구현 핵심

  • TwiML <Start><Stream />을 사용해 통화 오디오를 WebSocket으로 스트리밍
  • 서버에서 httpServer.upgrade를 통해 WebSocket 연결 수립
  • start / media / stop 이벤트 단위로 오디오 프레임 수신
  • μ-law 오디오를 PCM으로 변환 후 Azure STT 스트림 입력으로 전달

STT 결과 실시간 Push 구조

STT 인식 결과를 Socket 기반으로 즉시 프론트에 전달해 응답 지연을 최소화했습니다.
통화 중 실시간 자막 제공과 후속 응답 처리가 가능하도록 설계했습니다.

  • Polling 방식은 네트워크 오버헤드와 응답 지연 발생
  • Push 방식은 인식 완료 즉시 사용자에게 피드백 제공 가능

구현 핵심

  • Azure Speech SDK의 스트리밍 인식 방식 사용
  • STT 결과 수신 즉시 Socket 이벤트로 프론트엔드에 전달
  • 최근 인식 텍스트 + 시간 기준 중복 필터링 로직으로 반복 인식 방지

프론트엔드 연계

  • callSid 기준으로 Socket room 연결
  • STT 결과 수신 즉시 자막 표시
  • 인식 텍스트 기반 응답 추천 트리거 실행

서비스 단위 분리 설계 (Routes ↔ Services)

routes는 I/O 처리만 담당하고, services에 비즈니스 로직을 집중시켜 확장성과 유지보수성을 강화했습니다.
외부 벤더 의존성을 services 계층에 캡슐화해, STT·TTS·LLM 교체 시 코드 변경 범위를 최소화하도록 설계했습니다.

설계 원칙

  • routes
    • HTTP / Socket 이벤트 수신
    • 요청·응답 처리만 담당
  • services
    • 핵심 비즈니스 로직
    • 외부 API(STT, TTS, LLM, Twilio) 호출 담당
  • 벤더 교체 및 기능 확장 시 services 계층만 수정하도록 구조화

핵심 구현 예시

  • TTS
    • src/services/ttsService.js
    • synthesizeToFile()
  • 통화 제어
    • src/services/twilioService.js
    • playToCall()
  • 요약 처리
    • src/services/summaryService.js
    • summarizeCall()

트러블슈팅

OAuth 로그인 후 로그인 전 화면으로 되돌아가는 이슈

  • 원인: 서버는 세션 기반 인증(Passport)을 사용했으나, 프론트는 localStorage userId 기준으로 로그인 상태를 판단해 인증 기준이 불일치
  • 해결: 로그인 성공 후 /api/me를 통해 세션 사용자 정보를 조회하고, 프론트 상태를 세션 기준으로 재동기화하여 단일 인증 흐름으로 통합

사용자별 보이스 설정 구조를 단일 결정 흐름으로 개선

  • 원인: User 테이블에 보이스 정보를 포함할 경우 우선순위 관리가 어려움.
  • 해결: 보이스 설정을 UserVoiceSetting으로 분리하고, 프리셋 → 사용자 설정(DB) → 기본값 순으로 결정 로직을 명시

실시간 WebSocket 연결 끊김 및 재연결 이슈

  • 원인: 프론트–서버 WebSocket 연결이 끊길 경우, 소켓에 바인딩된 callSid / userId 컨텍스트가 유실되어 통화 중 이벤트(TTS 재생, 추천 멘트)가 정상 처리되지 않음
  • 해결: 소켓 연결 시 bind.call 이벤트로 통화 컨텍스트를 명시적으로 바인딩하고, 이벤트 처리 시 바인딩 여부를 검증하여 재연결 후에도 동일한 통화 단위(callSid) 기준으로 상태를 재동기화하도록 설계

Twilio 콜백 기반 통화 상태 불일치 이슈

  • 원인: 통화 종료/실패 이벤트가 Twilio 콜백, 사용자 종료 요청, WebSocket 이벤트 등 여러 경로로 분산되어 통화 상태 관리가 일관되지 않음
  • 해결: 통화 상태 변경을 Twilio statusCallback과 서버 콜백(/call-status)을 중심으로 단일화하고, 통화 종료 시 Socket Room(callSid) 기준으로 이벤트를 전파하여 클라이언트 상태를 일관되게 동기화

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

CallBa

콜포비아 사용자를 위한 전화 발신 보조 웹 서비스

프로젝트 개요

전화 통화에 대한 불안으로 발신이 부담스러운 사용자를 위해
사용자 입력 기반의 통화 목적을 음성(TTS)으로 대신 전달하고,
실시간 자막 및 상황별 응답 추천으로 통화 전반을 지원합니다.

팀 & 역할

팀명: Legend
기간: 2025.09.04 – 2025.12.18

하서경김나림윤예진임은서
프로필
역할팀장팀원팀원팀원
담당Backend 개발
Twilio 통화 제어
STT/TTS 연동
Backend 개발
LLM 기반 통화 내용 요약
UI/Frontend 개발
DB 설계 및 연동
로그인/인증 구현
UI/Frontend 개발
발표 및 시연

기술 스택

Frontend

  • HTML, CSS, Vanilla JavaScript
  • Socket.IO Client

Backend

  • Node.js / Express
  • WebSocket (Twilio Media Stream)
  • Socket.IO

Voice / AI

  • Twilio (Call, TwiML, Media Stream)
  • Azure Speech-to-Text (Streaming)
  • Gemini (대화 추천 멘트, 통화 요약)
  • ElevenLabs (TTS)

Database / Infra

  • PostgreSQL
  • Prisma ORM
  • Docker / Docker Compose
  • ngrok

핵심 기능

전화 발신 및 통화 흐름 제어

  • 웹에서 발신 요청 시 Twilio Call API로 전화 연결
  • TwiML을 통해 통화 시작, 대기, 음성 재생 등 통화 흐름 제어
  • 통화 상태 콜백을 수신해 실시간 상태 동기화

실시간 음성 인식 및 자막 제공

  • Twilio Media Stream을 WebSocket으로 수신
  • μ-law 음성을 PCM으로 변환 후 Azure STT로 실시간 음성 인식
  • 인식 결과를 자막 형태로 프론트에 실시간 전달

AI 기반 대화 지원

  • STT 결과를 기반으로 Gemini가 상황별 추천 답변 생성
  • 추천 멘트를 프론트에 실시간 제공하여 전화 응대 보조

TTS 생성 및 재생

  • 추천 멘트 또는 사용자 입력 문장을 ElevenLabs TTS로 음성 생성
  • 생성된 음성을 통화 중 실시간 재생하거나 웹에서 미리듣기 지원

통화 요약 및 기록 관리

  • 통화 종료 시 전체 대화를 Gemini로 요약
  • 요약 및 대화 기록을 DB에 저장하고 조회 가능

실시간 음성 처리 아키텍처

  1. 웹에서 전화 발신 요청
  2. Twilio가 전화를 연결하고 Media Stream으로 음성 송출
  3. 서버가 음성을 WebSocket으로 수신 후 STT 수행
  4. 인식된 텍스트로 추천 멘트 생성 및 실시간 전달
  5. 선택된 멘트를 TTS로 생성해 통화 중 재생
  6. 통화 종료 후 요약 생성 및 저장

파이프라인 요약:
Twilio Media Stream → WebSocket → Azure STT → Gemini → Socket.IO → (ElevenLabs TTS)

ERD

프로젝트 구조

전체 디렉터리 구조
front/ # Node(Express) 서버 + 정적 프론트(바닐라 JS) 통합 디렉터리
├─ public/ # 프론트 정적 리소스(HTML/CSS/JS)
├─ src/ # 서버 핵심 로직
│ ├─ routes/ # HTTP API 라우트(auth/user/call/twilio)
│ ├─ sockets/ # 실시간 처리(WebSocket: Media Stream, Socket.IO)
│ ├─ services/ # 외부 연동 로직(Twilio, ElevenLabs, Gemini 등)
│ ├─ config/ # 환경변수 및 외부 클라이언트 초기화
│ ├─ auth/ # Google OAuth 설정
│ ├─ db/ # Prisma 클라이언트
│ └─ utils/ # 오디오 유틸(μ-law→PCM 변환 등)
├─ audio/ # TTS로 생성된 mp3 파일 정적 서빙
├─ prisma/ # Prisma 스키마/마이그레이션
├─ server.js # 서버 엔트리
├─ docker-compose.yml # 로컬 Postgres 실행
└─ .env.example # 환경변수 템플릿
디렉터리 구조 상세
front/
audio/ # TTS로 생성된 오디오(mp3) 정적 서빙 디렉터리
node_modules/ # 의존성
prisma/ # Prisma schema 및 migration 관리
public/ # 프론트 정적 리소스
css/ # 공통 스타일
javascript/ # 브라우저 스크립트
pages/ # HTML 페이지
src/ # 서버 핵심 로직
auth/
passport.js # Google OAuth 전략/세션 설정
config/
env.js # 환경변수 로딩, 외부 API 클라이언트 초기화
voice.js # 음성 프리셋 매핑/관리
db/
prisma.js # Prisma Client 초기화 및 DB 연결
routes/
authRoutes.js # 로그인, 콜백, 로그아웃 등
callRoutes.js # 통화 생성, 상태 관리, 미리듣기
twilioRoutes.js # TwiML 응답 및 Twilio 콜백
userRoutes.js # 프로필 조회, 보이스 설정
services/
ttsService.js # ElevenLabs TTS 호출, 오디오 파일 생성/저장
twilioService.js # Twilio Call 생성/업데이트/제어
summaryService.js # 통화 요약 (Gemini)
sockets/
mediaSocket.js # Twilio Media Stream WebSocket 수신 → STT(Azure) → 추천(Gemini) 파이프라인
frontendSocket.js # 프론트로 실시간 이벤트 Push(자막/추천멘트/통화 상태)
utils/
audio.js # 오디오 파일 경로/입출력 유틸
mulaw.js # 오디오 전처리: μ-law → PCM 변환
.env
.env.example # 환경변수 템플릿
.gitignore
docker-compose.yml # 로컬 Postgres 실행 설정
package-lock.json
prisma.config.ts
README.md
server.js # 서버 엔트리: Express 설정 + 라우트/소켓 초기화

실행 방법

1️⃣ 사전 준비

  • Node.js (권장: LTS)
  • Docker / Docker Compose
  • ngrok
  • 계정 및 API Key: Twilio, Azure STT, ElevenLabs, Gemini API

2️⃣ 환경변수 설정

프로젝트 루트(front/)에 .env 파일을 생성하고 아래 값을 설정합니다.
필수 값은 .env.example 파일을 참고하세요.

환경변수 목록 보기
# Server
PORT=3003
PUBLIC_HOST=
SERVER_URL=
# Twilio
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_API_KEY=
TWILIO_API_SECRET=
TWILIO_TWIML_APP_SID=
TWILIO_FROM_NUMBER=
TWILIO_CALLER_ID=
TWILIO_MEDIA_STREAM_URL= # (선택) 기본적으로 PUBLIC_HOST 기반 동작# STT / LLM
AZURE_SPEECH_KEY=
AZURE_SPEECH_REGION=
GEMINI_API_KEY=
# ElevenLabs
ELEVENLABS_API_KEY=
ELEVENLABS_MODEL_ID=
ELEVENLABS_VOICE_ID=
# ElevenLabs Voice Presets
ELEVENLABS_VOICE_ID_BRIGHT=
ELEVENLABS_VOICE_ID_CLEAR=
ELEVENLABS_VOICE_ID_CALM=
ELEVENLABS_VOICE_ID_WARM=
# Database
DATABASE_URL=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
POSTGRES_PORT=
POSTGRES_HOST=
# Auth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
SESSION_SECRET=

3️⃣ 서버 실행

npm install
docker-compose up -d # Postgres 실행
node server.js # 서버 실행

4️⃣ 외부 접근 설정 (ngrok)

Twilio Webhook 및 Media Stream 연결을 위해 ngrok으로 서버를 외부에 노출합니다. ngrok에서 발급된 URL을 PUBLIC_HOST 환경변수에 설정합니다.

ngrok http 3003

주요 엔드포인트

구분MethodPath설명
ClientPOST/calls전화 발신 요청 (시작 멘트 생성 후 Twilio 발신)
ClientGET/calls/history통화 기록 조회
ClientPOST/tts-previewTTS 미리듣기
TwilioPOST/call-status통화 상태 콜백
TwilioALL/twilio/answerTwiML 응답 (스트림 시작 + 시작 멘트 재생)
TwilioALL/twilio/holdTwiML 응답 (대기 유지 + 스트림 유지)

기술적 의사결정

Media Stream 기반 실시간 음성 처리

HTTP 업로드·폴링 방식 대신 Twilio Media Streams(WebSocket) 구조를 적용해,
통화 음성을 프레임 단위로 수신하며 STT 지연을 최소화했습니다.

  • HTTP 기반 업로드 방식은 음성 버퍼링 및 전송 지연 발생
  • WebSocket 스트리밍은 통화 흐름을 유지한 채 즉시 음성 처리 가능

구현 핵심

  • TwiML <Start><Stream />을 사용해 통화 오디오를 WebSocket으로 스트리밍
  • 서버에서 httpServer.upgrade를 통해 WebSocket 연결 수립
  • start / media / stop 이벤트 단위로 오디오 프레임 수신
  • μ-law 오디오를 PCM으로 변환 후 Azure STT 스트림 입력으로 전달

STT 결과 실시간 Push 구조

STT 인식 결과를 Socket 기반으로 즉시 프론트에 전달해 응답 지연을 최소화했습니다.
통화 중 실시간 자막 제공과 후속 응답 처리가 가능하도록 설계했습니다.

  • Polling 방식은 네트워크 오버헤드와 응답 지연 발생
  • Push 방식은 인식 완료 즉시 사용자에게 피드백 제공 가능

구현 핵심

  • Azure Speech SDK의 스트리밍 인식 방식 사용
  • STT 결과 수신 즉시 Socket 이벤트로 프론트엔드에 전달
  • 최근 인식 텍스트 + 시간 기준 중복 필터링 로직으로 반복 인식 방지

프론트엔드 연계

  • callSid 기준으로 Socket room 연결
  • STT 결과 수신 즉시 자막 표시
  • 인식 텍스트 기반 응답 추천 트리거 실행

서비스 단위 분리 설계 (Routes ↔ Services)

routes는 I/O 처리만 담당하고, services에 비즈니스 로직을 집중시켜 확장성과 유지보수성을 강화했습니다.
외부 벤더 의존성을 services 계층에 캡슐화해, STT·TTS·LLM 교체 시 코드 변경 범위를 최소화하도록 설계했습니다.

설계 원칙

  • routes
    • HTTP / Socket 이벤트 수신
    • 요청·응답 처리만 담당
  • services
    • 핵심 비즈니스 로직
    • 외부 API(STT, TTS, LLM, Twilio) 호출 담당
  • 벤더 교체 및 기능 확장 시 services 계층만 수정하도록 구조화

핵심 구현 예시

  • TTS
    • src/services/ttsService.js
    • synthesizeToFile()
  • 통화 제어
    • src/services/twilioService.js
    • playToCall()
  • 요약 처리
    • src/services/summaryService.js
    • summarizeCall()

트러블슈팅

OAuth 로그인 후 로그인 전 화면으로 되돌아가는 이슈

  • 원인: 서버는 세션 기반 인증(Passport)을 사용했으나, 프론트는 localStorage userId 기준으로 로그인 상태를 판단해 인증 기준이 불일치
  • 해결: 로그인 성공 후 /api/me를 통해 세션 사용자 정보를 조회하고, 프론트 상태를 세션 기준으로 재동기화하여 단일 인증 흐름으로 통합

사용자별 보이스 설정 구조를 단일 결정 흐름으로 개선

  • 원인: User 테이블에 보이스 정보를 포함할 경우 우선순위 관리가 어려움.
  • 해결: 보이스 설정을 UserVoiceSetting으로 분리하고, 프리셋 → 사용자 설정(DB) → 기본값 순으로 결정 로직을 명시

실시간 WebSocket 연결 끊김 및 재연결 이슈

  • 원인: 프론트–서버 WebSocket 연결이 끊길 경우, 소켓에 바인딩된 callSid / userId 컨텍스트가 유실되어 통화 중 이벤트(TTS 재생, 추천 멘트)가 정상 처리되지 않음
  • 해결: 소켓 연결 시 bind.call 이벤트로 통화 컨텍스트를 명시적으로 바인딩하고, 이벤트 처리 시 바인딩 여부를 검증하여 재연결 후에도 동일한 통화 단위(callSid) 기준으로 상태를 재동기화하도록 설계

Twilio 콜백 기반 통화 상태 불일치 이슈

  • 원인: 통화 종료/실패 이벤트가 Twilio 콜백, 사용자 종료 요청, WebSocket 이벤트 등 여러 경로로 분산되어 통화 상태 관리가 일관되지 않음
  • 해결: 통화 상태 변경을 Twilio statusCallback과 서버 콜백(/call-status)을 중심으로 단일화하고, 통화 종료 시 Socket Room(callSid) 기준으로 이벤트를 전파하여 클라이언트 상태를 일관되게 동기화

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CallBa

콜포비아 사용자를 위한 전화 발신 보조 웹 서비스

프로젝트 개요

전화 통화에 대한 불안으로 발신이 부담스러운 사용자를 위해
사용자 입력 기반의 통화 목적을 음성(TTS)으로 대신 전달하고,
실시간 자막 및 상황별 응답 추천으로 통화 전반을 지원합니다.

팀 & 역할

팀명: Legend
기간: 2025.09.04 – 2025.12.18

하서경김나림윤예진임은서
프로필
역할팀장팀원팀원팀원
담당Backend 개발
Twilio 통화 제어
STT/TTS 연동
Backend 개발
LLM 기반 통화 내용 요약
UI/Frontend 개발
DB 설계 및 연동
로그인/인증 구현
UI/Frontend 개발
발표 및 시연

기술 스택

Frontend

  • HTML, CSS, Vanilla JavaScript
  • Socket.IO Client

Backend

  • Node.js / Express
  • WebSocket (Twilio Media Stream)
  • Socket.IO

Voice / AI

  • Twilio (Call, TwiML, Media Stream)
  • Azure Speech-to-Text (Streaming)
  • Gemini (대화 추천 멘트, 통화 요약)
  • ElevenLabs (TTS)

Database / Infra

  • PostgreSQL
  • Prisma ORM
  • Docker / Docker Compose
  • ngrok

핵심 기능

전화 발신 및 통화 흐름 제어

  • 웹에서 발신 요청 시 Twilio Call API로 전화 연결
  • TwiML을 통해 통화 시작, 대기, 음성 재생 등 통화 흐름 제어
  • 통화 상태 콜백을 수신해 실시간 상태 동기화

실시간 음성 인식 및 자막 제공

  • Twilio Media Stream을 WebSocket으로 수신
  • μ-law 음성을 PCM으로 변환 후 Azure STT로 실시간 음성 인식
  • 인식 결과를 자막 형태로 프론트에 실시간 전달

AI 기반 대화 지원

  • STT 결과를 기반으로 Gemini가 상황별 추천 답변 생성
  • 추천 멘트를 프론트에 실시간 제공하여 전화 응대 보조

TTS 생성 및 재생

  • 추천 멘트 또는 사용자 입력 문장을 ElevenLabs TTS로 음성 생성
  • 생성된 음성을 통화 중 실시간 재생하거나 웹에서 미리듣기 지원

통화 요약 및 기록 관리

  • 통화 종료 시 전체 대화를 Gemini로 요약
  • 요약 및 대화 기록을 DB에 저장하고 조회 가능

실시간 음성 처리 아키텍처

  1. 웹에서 전화 발신 요청
  2. Twilio가 전화를 연결하고 Media Stream으로 음성 송출
  3. 서버가 음성을 WebSocket으로 수신 후 STT 수행
  4. 인식된 텍스트로 추천 멘트 생성 및 실시간 전달
  5. 선택된 멘트를 TTS로 생성해 통화 중 재생
  6. 통화 종료 후 요약 생성 및 저장

파이프라인 요약:
Twilio Media Stream → WebSocket → Azure STT → Gemini → Socket.IO → (ElevenLabs TTS)

ERD

프로젝트 구조

전체 디렉터리 구조
front/ # Node(Express) 서버 + 정적 프론트(바닐라 JS) 통합 디렉터리
├─ public/ # 프론트 정적 리소스(HTML/CSS/JS)
├─ src/ # 서버 핵심 로직
│ ├─ routes/ # HTTP API 라우트(auth/user/call/twilio)
│ ├─ sockets/ # 실시간 처리(WebSocket: Media Stream, Socket.IO)
│ ├─ services/ # 외부 연동 로직(Twilio, ElevenLabs, Gemini 등)
│ ├─ config/ # 환경변수 및 외부 클라이언트 초기화
│ ├─ auth/ # Google OAuth 설정
│ ├─ db/ # Prisma 클라이언트
│ └─ utils/ # 오디오 유틸(μ-law→PCM 변환 등)
├─ audio/ # TTS로 생성된 mp3 파일 정적 서빙
├─ prisma/ # Prisma 스키마/마이그레이션
├─ server.js # 서버 엔트리
├─ docker-compose.yml # 로컬 Postgres 실행
└─ .env.example # 환경변수 템플릿
디렉터리 구조 상세
front/
audio/ # TTS로 생성된 오디오(mp3) 정적 서빙 디렉터리
node_modules/ # 의존성
prisma/ # Prisma schema 및 migration 관리
public/ # 프론트 정적 리소스
css/ # 공통 스타일
javascript/ # 브라우저 스크립트
pages/ # HTML 페이지
src/ # 서버 핵심 로직
auth/
passport.js # Google OAuth 전략/세션 설정
config/
env.js # 환경변수 로딩, 외부 API 클라이언트 초기화
voice.js # 음성 프리셋 매핑/관리
db/
prisma.js # Prisma Client 초기화 및 DB 연결
routes/
authRoutes.js # 로그인, 콜백, 로그아웃 등
callRoutes.js # 통화 생성, 상태 관리, 미리듣기
twilioRoutes.js # TwiML 응답 및 Twilio 콜백
userRoutes.js # 프로필 조회, 보이스 설정
services/
ttsService.js # ElevenLabs TTS 호출, 오디오 파일 생성/저장
twilioService.js # Twilio Call 생성/업데이트/제어
summaryService.js # 통화 요약 (Gemini)
sockets/
mediaSocket.js # Twilio Media Stream WebSocket 수신 → STT(Azure) → 추천(Gemini) 파이프라인
frontendSocket.js # 프론트로 실시간 이벤트 Push(자막/추천멘트/통화 상태)
utils/
audio.js # 오디오 파일 경로/입출력 유틸
mulaw.js # 오디오 전처리: μ-law → PCM 변환
.env
.env.example # 환경변수 템플릿
.gitignore
docker-compose.yml # 로컬 Postgres 실행 설정
package-lock.json
prisma.config.ts
README.md
server.js # 서버 엔트리: Express 설정 + 라우트/소켓 초기화

실행 방법

1️⃣ 사전 준비

  • Node.js (권장: LTS)
  • Docker / Docker Compose
  • ngrok
  • 계정 및 API Key: Twilio, Azure STT, ElevenLabs, Gemini API

2️⃣ 환경변수 설정

프로젝트 루트(front/)에 .env 파일을 생성하고 아래 값을 설정합니다.
필수 값은 .env.example 파일을 참고하세요.

환경변수 목록 보기
# Server
PORT=3003
PUBLIC_HOST=
SERVER_URL=
# Twilio
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_API_KEY=
TWILIO_API_SECRET=
TWILIO_TWIML_APP_SID=
TWILIO_FROM_NUMBER=
TWILIO_CALLER_ID=
TWILIO_MEDIA_STREAM_URL= # (선택) 기본적으로 PUBLIC_HOST 기반 동작# STT / LLM
AZURE_SPEECH_KEY=
AZURE_SPEECH_REGION=
GEMINI_API_KEY=
# ElevenLabs
ELEVENLABS_API_KEY=
ELEVENLABS_MODEL_ID=
ELEVENLABS_VOICE_ID=
# ElevenLabs Voice Presets
ELEVENLABS_VOICE_ID_BRIGHT=
ELEVENLABS_VOICE_ID_CLEAR=
ELEVENLABS_VOICE_ID_CALM=
ELEVENLABS_VOICE_ID_WARM=
# Database
DATABASE_URL=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
POSTGRES_PORT=
POSTGRES_HOST=
# Auth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
SESSION_SECRET=

3️⃣ 서버 실행

npm install
docker-compose up -d # Postgres 실행
node server.js # 서버 실행

4️⃣ 외부 접근 설정 (ngrok)

Twilio Webhook 및 Media Stream 연결을 위해 ngrok으로 서버를 외부에 노출합니다. ngrok에서 발급된 URL을 PUBLIC_HOST 환경변수에 설정합니다.

ngrok http 3003

주요 엔드포인트

구분MethodPath설명
ClientPOST/calls전화 발신 요청 (시작 멘트 생성 후 Twilio 발신)
ClientGET/calls/history통화 기록 조회
ClientPOST/tts-previewTTS 미리듣기
TwilioPOST/call-status통화 상태 콜백
TwilioALL/twilio/answerTwiML 응답 (스트림 시작 + 시작 멘트 재생)
TwilioALL/twilio/holdTwiML 응답 (대기 유지 + 스트림 유지)

기술적 의사결정

Media Stream 기반 실시간 음성 처리

HTTP 업로드·폴링 방식 대신 Twilio Media Streams(WebSocket) 구조를 적용해,
통화 음성을 프레임 단위로 수신하며 STT 지연을 최소화했습니다.

  • HTTP 기반 업로드 방식은 음성 버퍼링 및 전송 지연 발생
  • WebSocket 스트리밍은 통화 흐름을 유지한 채 즉시 음성 처리 가능

구현 핵심

  • TwiML <Start><Stream />을 사용해 통화 오디오를 WebSocket으로 스트리밍
  • 서버에서 httpServer.upgrade를 통해 WebSocket 연결 수립
  • start / media / stop 이벤트 단위로 오디오 프레임 수신
  • μ-law 오디오를 PCM으로 변환 후 Azure STT 스트림 입력으로 전달

STT 결과 실시간 Push 구조

STT 인식 결과를 Socket 기반으로 즉시 프론트에 전달해 응답 지연을 최소화했습니다.
통화 중 실시간 자막 제공과 후속 응답 처리가 가능하도록 설계했습니다.

  • Polling 방식은 네트워크 오버헤드와 응답 지연 발생
  • Push 방식은 인식 완료 즉시 사용자에게 피드백 제공 가능

구현 핵심

  • Azure Speech SDK의 스트리밍 인식 방식 사용
  • STT 결과 수신 즉시 Socket 이벤트로 프론트엔드에 전달
  • 최근 인식 텍스트 + 시간 기준 중복 필터링 로직으로 반복 인식 방지

프론트엔드 연계

  • callSid 기준으로 Socket room 연결
  • STT 결과 수신 즉시 자막 표시
  • 인식 텍스트 기반 응답 추천 트리거 실행

서비스 단위 분리 설계 (Routes ↔ Services)

routes는 I/O 처리만 담당하고, services에 비즈니스 로직을 집중시켜 확장성과 유지보수성을 강화했습니다.
외부 벤더 의존성을 services 계층에 캡슐화해, STT·TTS·LLM 교체 시 코드 변경 범위를 최소화하도록 설계했습니다.

설계 원칙

  • routes
    • HTTP / Socket 이벤트 수신
    • 요청·응답 처리만 담당
  • services
    • 핵심 비즈니스 로직
    • 외부 API(STT, TTS, LLM, Twilio) 호출 담당
  • 벤더 교체 및 기능 확장 시 services 계층만 수정하도록 구조화

핵심 구현 예시

  • TTS
    • src/services/ttsService.js
    • synthesizeToFile()
  • 통화 제어
    • src/services/twilioService.js
    • playToCall()
  • 요약 처리
    • src/services/summaryService.js
    • summarizeCall()

트러블슈팅

OAuth 로그인 후 로그인 전 화면으로 되돌아가는 이슈

  • 원인: 서버는 세션 기반 인증(Passport)을 사용했으나, 프론트는 localStorage userId 기준으로 로그인 상태를 판단해 인증 기준이 불일치
  • 해결: 로그인 성공 후 /api/me를 통해 세션 사용자 정보를 조회하고, 프론트 상태를 세션 기준으로 재동기화하여 단일 인증 흐름으로 통합

사용자별 보이스 설정 구조를 단일 결정 흐름으로 개선

  • 원인: User 테이블에 보이스 정보를 포함할 경우 우선순위 관리가 어려움.
  • 해결: 보이스 설정을 UserVoiceSetting으로 분리하고, 프리셋 → 사용자 설정(DB) → 기본값 순으로 결정 로직을 명시

실시간 WebSocket 연결 끊김 및 재연결 이슈

  • 원인: 프론트–서버 WebSocket 연결이 끊길 경우, 소켓에 바인딩된 callSid / userId 컨텍스트가 유실되어 통화 중 이벤트(TTS 재생, 추천 멘트)가 정상 처리되지 않음
  • 해결: 소켓 연결 시 bind.call 이벤트로 통화 컨텍스트를 명시적으로 바인딩하고, 이벤트 처리 시 바인딩 여부를 검증하여 재연결 후에도 동일한 통화 단위(callSid) 기준으로 상태를 재동기화하도록 설계

Twilio 콜백 기반 통화 상태 불일치 이슈

  • 원인: 통화 종료/실패 이벤트가 Twilio 콜백, 사용자 종료 요청, WebSocket 이벤트 등 여러 경로로 분산되어 통화 상태 관리가 일관되지 않음
  • 해결: 통화 상태 변경을 Twilio statusCallback과 서버 콜백(/call-status)을 중심으로 단일화하고, 통화 종료 시 Socket Room(callSid) 기준으로 이벤트를 전파하여 클라이언트 상태를 일관되게 동기화

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CallBa

콜포비아 사용자를 위한 전화 발신 보조 웹 서비스

프로젝트 개요

전화 통화에 대한 불안으로 발신이 부담스러운 사용자를 위해
사용자 입력 기반의 통화 목적을 음성(TTS)으로 대신 전달하고,
실시간 자막 및 상황별 응답 추천으로 통화 전반을 지원합니다.

팀 & 역할

팀명: Legend
기간: 2025.09.04 – 2025.12.18

하서경김나림윤예진임은서
프로필
역할팀장팀원팀원팀원
담당Backend 개발
Twilio 통화 제어
STT/TTS 연동
Backend 개발
LLM 기반 통화 내용 요약
UI/Frontend 개발
DB 설계 및 연동
로그인/인증 구현
UI/Frontend 개발
발표 및 시연

기술 스택

Frontend

  • HTML, CSS, Vanilla JavaScript
  • Socket.IO Client

Backend

  • Node.js / Express
  • WebSocket (Twilio Media Stream)
  • Socket.IO

Voice / AI

  • Twilio (Call, TwiML, Media Stream)
  • Azure Speech-to-Text (Streaming)
  • Gemini (대화 추천 멘트, 통화 요약)
  • ElevenLabs (TTS)

Database / Infra

  • PostgreSQL
  • Prisma ORM
  • Docker / Docker Compose
  • ngrok

핵심 기능

전화 발신 및 통화 흐름 제어

  • 웹에서 발신 요청 시 Twilio Call API로 전화 연결
  • TwiML을 통해 통화 시작, 대기, 음성 재생 등 통화 흐름 제어
  • 통화 상태 콜백을 수신해 실시간 상태 동기화

실시간 음성 인식 및 자막 제공

  • Twilio Media Stream을 WebSocket으로 수신
  • μ-law 음성을 PCM으로 변환 후 Azure STT로 실시간 음성 인식
  • 인식 결과를 자막 형태로 프론트에 실시간 전달

AI 기반 대화 지원

  • STT 결과를 기반으로 Gemini가 상황별 추천 답변 생성
  • 추천 멘트를 프론트에 실시간 제공하여 전화 응대 보조

TTS 생성 및 재생

  • 추천 멘트 또는 사용자 입력 문장을 ElevenLabs TTS로 음성 생성
  • 생성된 음성을 통화 중 실시간 재생하거나 웹에서 미리듣기 지원

통화 요약 및 기록 관리

  • 통화 종료 시 전체 대화를 Gemini로 요약
  • 요약 및 대화 기록을 DB에 저장하고 조회 가능

실시간 음성 처리 아키텍처

  1. 웹에서 전화 발신 요청
  2. Twilio가 전화를 연결하고 Media Stream으로 음성 송출
  3. 서버가 음성을 WebSocket으로 수신 후 STT 수행
  4. 인식된 텍스트로 추천 멘트 생성 및 실시간 전달
  5. 선택된 멘트를 TTS로 생성해 통화 중 재생
  6. 통화 종료 후 요약 생성 및 저장

파이프라인 요약:
Twilio Media Stream → WebSocket → Azure STT → Gemini → Socket.IO → (ElevenLabs TTS)

ERD

프로젝트 구조

전체 디렉터리 구조
front/ # Node(Express) 서버 + 정적 프론트(바닐라 JS) 통합 디렉터리
├─ public/ # 프론트 정적 리소스(HTML/CSS/JS)
├─ src/ # 서버 핵심 로직
│ ├─ routes/ # HTTP API 라우트(auth/user/call/twilio)
│ ├─ sockets/ # 실시간 처리(WebSocket: Media Stream, Socket.IO)
│ ├─ services/ # 외부 연동 로직(Twilio, ElevenLabs, Gemini 등)
│ ├─ config/ # 환경변수 및 외부 클라이언트 초기화
│ ├─ auth/ # Google OAuth 설정
│ ├─ db/ # Prisma 클라이언트
│ └─ utils/ # 오디오 유틸(μ-law→PCM 변환 등)
├─ audio/ # TTS로 생성된 mp3 파일 정적 서빙
├─ prisma/ # Prisma 스키마/마이그레이션
├─ server.js # 서버 엔트리
├─ docker-compose.yml # 로컬 Postgres 실행
└─ .env.example # 환경변수 템플릿
디렉터리 구조 상세
front/
audio/ # TTS로 생성된 오디오(mp3) 정적 서빙 디렉터리
node_modules/ # 의존성
prisma/ # Prisma schema 및 migration 관리
public/ # 프론트 정적 리소스
css/ # 공통 스타일
javascript/ # 브라우저 스크립트
pages/ # HTML 페이지
src/ # 서버 핵심 로직
auth/
passport.js # Google OAuth 전략/세션 설정
config/
env.js # 환경변수 로딩, 외부 API 클라이언트 초기화
voice.js # 음성 프리셋 매핑/관리
db/
prisma.js # Prisma Client 초기화 및 DB 연결
routes/
authRoutes.js # 로그인, 콜백, 로그아웃 등
callRoutes.js # 통화 생성, 상태 관리, 미리듣기
twilioRoutes.js # TwiML 응답 및 Twilio 콜백
userRoutes.js # 프로필 조회, 보이스 설정
services/
ttsService.js # ElevenLabs TTS 호출, 오디오 파일 생성/저장
twilioService.js # Twilio Call 생성/업데이트/제어
summaryService.js # 통화 요약 (Gemini)
sockets/
mediaSocket.js # Twilio Media Stream WebSocket 수신 → STT(Azure) → 추천(Gemini) 파이프라인
frontendSocket.js # 프론트로 실시간 이벤트 Push(자막/추천멘트/통화 상태)
utils/
audio.js # 오디오 파일 경로/입출력 유틸
mulaw.js # 오디오 전처리: μ-law → PCM 변환
.env
.env.example # 환경변수 템플릿
.gitignore
docker-compose.yml # 로컬 Postgres 실행 설정
package-lock.json
prisma.config.ts
README.md
server.js # 서버 엔트리: Express 설정 + 라우트/소켓 초기화

실행 방법

1️⃣ 사전 준비

  • Node.js (권장: LTS)
  • Docker / Docker Compose
  • ngrok
  • 계정 및 API Key: Twilio, Azure STT, ElevenLabs, Gemini API

2️⃣ 환경변수 설정

프로젝트 루트(front/)에 .env 파일을 생성하고 아래 값을 설정합니다.
필수 값은 .env.example 파일을 참고하세요.

환경변수 목록 보기
# Server
PORT=3003
PUBLIC_HOST=
SERVER_URL=
# Twilio
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_API_KEY=
TWILIO_API_SECRET=
TWILIO_TWIML_APP_SID=
TWILIO_FROM_NUMBER=
TWILIO_CALLER_ID=
TWILIO_MEDIA_STREAM_URL= # (선택) 기본적으로 PUBLIC_HOST 기반 동작# STT / LLM
AZURE_SPEECH_KEY=
AZURE_SPEECH_REGION=
GEMINI_API_KEY=
# ElevenLabs
ELEVENLABS_API_KEY=
ELEVENLABS_MODEL_ID=
ELEVENLABS_VOICE_ID=
# ElevenLabs Voice Presets
ELEVENLABS_VOICE_ID_BRIGHT=
ELEVENLABS_VOICE_ID_CLEAR=
ELEVENLABS_VOICE_ID_CALM=
ELEVENLABS_VOICE_ID_WARM=
# Database
DATABASE_URL=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
POSTGRES_PORT=
POSTGRES_HOST=
# Auth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
SESSION_SECRET=

3️⃣ 서버 실행

npm install
docker-compose up -d # Postgres 실행
node server.js # 서버 실행

4️⃣ 외부 접근 설정 (ngrok)

Twilio Webhook 및 Media Stream 연결을 위해 ngrok으로 서버를 외부에 노출합니다. ngrok에서 발급된 URL을 PUBLIC_HOST 환경변수에 설정합니다.

ngrok http 3003

주요 엔드포인트

구분MethodPath설명
ClientPOST/calls전화 발신 요청 (시작 멘트 생성 후 Twilio 발신)
ClientGET/calls/history통화 기록 조회
ClientPOST/tts-previewTTS 미리듣기
TwilioPOST/call-status통화 상태 콜백
TwilioALL/twilio/answerTwiML 응답 (스트림 시작 + 시작 멘트 재생)
TwilioALL/twilio/holdTwiML 응답 (대기 유지 + 스트림 유지)

기술적 의사결정

Media Stream 기반 실시간 음성 처리

HTTP 업로드·폴링 방식 대신 Twilio Media Streams(WebSocket) 구조를 적용해,
통화 음성을 프레임 단위로 수신하며 STT 지연을 최소화했습니다.

  • HTTP 기반 업로드 방식은 음성 버퍼링 및 전송 지연 발생
  • WebSocket 스트리밍은 통화 흐름을 유지한 채 즉시 음성 처리 가능

구현 핵심

  • TwiML <Start><Stream />을 사용해 통화 오디오를 WebSocket으로 스트리밍
  • 서버에서 httpServer.upgrade를 통해 WebSocket 연결 수립
  • start / media / stop 이벤트 단위로 오디오 프레임 수신
  • μ-law 오디오를 PCM으로 변환 후 Azure STT 스트림 입력으로 전달

STT 결과 실시간 Push 구조

STT 인식 결과를 Socket 기반으로 즉시 프론트에 전달해 응답 지연을 최소화했습니다.
통화 중 실시간 자막 제공과 후속 응답 처리가 가능하도록 설계했습니다.

  • Polling 방식은 네트워크 오버헤드와 응답 지연 발생
  • Push 방식은 인식 완료 즉시 사용자에게 피드백 제공 가능

구현 핵심

  • Azure Speech SDK의 스트리밍 인식 방식 사용
  • STT 결과 수신 즉시 Socket 이벤트로 프론트엔드에 전달
  • 최근 인식 텍스트 + 시간 기준 중복 필터링 로직으로 반복 인식 방지

프론트엔드 연계

  • callSid 기준으로 Socket room 연결
  • STT 결과 수신 즉시 자막 표시
  • 인식 텍스트 기반 응답 추천 트리거 실행

서비스 단위 분리 설계 (Routes ↔ Services)

routes는 I/O 처리만 담당하고, services에 비즈니스 로직을 집중시켜 확장성과 유지보수성을 강화했습니다.
외부 벤더 의존성을 services 계층에 캡슐화해, STT·TTS·LLM 교체 시 코드 변경 범위를 최소화하도록 설계했습니다.

설계 원칙

  • routes
    • HTTP / Socket 이벤트 수신
    • 요청·응답 처리만 담당
  • services
    • 핵심 비즈니스 로직
    • 외부 API(STT, TTS, LLM, Twilio) 호출 담당
  • 벤더 교체 및 기능 확장 시 services 계층만 수정하도록 구조화

핵심 구현 예시

  • TTS
    • src/services/ttsService.js
    • synthesizeToFile()
  • 통화 제어
    • src/services/twilioService.js
    • playToCall()
  • 요약 처리
    • src/services/summaryService.js
    • summarizeCall()

트러블슈팅

OAuth 로그인 후 로그인 전 화면으로 되돌아가는 이슈

  • 원인: 서버는 세션 기반 인증(Passport)을 사용했으나, 프론트는 localStorage userId 기준으로 로그인 상태를 판단해 인증 기준이 불일치
  • 해결: 로그인 성공 후 /api/me를 통해 세션 사용자 정보를 조회하고, 프론트 상태를 세션 기준으로 재동기화하여 단일 인증 흐름으로 통합

사용자별 보이스 설정 구조를 단일 결정 흐름으로 개선

  • 원인: User 테이블에 보이스 정보를 포함할 경우 우선순위 관리가 어려움.
  • 해결: 보이스 설정을 UserVoiceSetting으로 분리하고, 프리셋 → 사용자 설정(DB) → 기본값 순으로 결정 로직을 명시

실시간 WebSocket 연결 끊김 및 재연결 이슈

  • 원인: 프론트–서버 WebSocket 연결이 끊길 경우, 소켓에 바인딩된 callSid / userId 컨텍스트가 유실되어 통화 중 이벤트(TTS 재생, 추천 멘트)가 정상 처리되지 않음
  • 해결: 소켓 연결 시 bind.call 이벤트로 통화 컨텍스트를 명시적으로 바인딩하고, 이벤트 처리 시 바인딩 여부를 검증하여 재연결 후에도 동일한 통화 단위(callSid) 기준으로 상태를 재동기화하도록 설계

Twilio 콜백 기반 통화 상태 불일치 이슈

  • 원인: 통화 종료/실패 이벤트가 Twilio 콜백, 사용자 종료 요청, WebSocket 이벤트 등 여러 경로로 분산되어 통화 상태 관리가 일관되지 않음
  • 해결: 통화 상태 변경을 Twilio statusCallback과 서버 콜백(/call-status)을 중심으로 단일화하고, 통화 종료 시 Socket Room(callSid) 기준으로 이벤트를 전파하여 클라이언트 상태를 일관되게 동기화

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

CallBa

콜포비아 사용자를 위한 전화 발신 보조 웹 서비스

프로젝트 개요

전화 통화에 대한 불안으로 발신이 부담스러운 사용자를 위해
사용자 입력 기반의 통화 목적을 음성(TTS)으로 대신 전달하고,
실시간 자막 및 상황별 응답 추천으로 통화 전반을 지원합니다.

팀 & 역할

팀명: Legend
기간: 2025.09.04 – 2025.12.18

하서경김나림윤예진임은서
프로필
역할팀장팀원팀원팀원
담당Backend 개발
Twilio 통화 제어
STT/TTS 연동
Backend 개발
LLM 기반 통화 내용 요약
UI/Frontend 개발
DB 설계 및 연동
로그인/인증 구현
UI/Frontend 개발
발표 및 시연

기술 스택

Frontend

  • HTML, CSS, Vanilla JavaScript
  • Socket.IO Client

Backend

  • Node.js / Express
  • WebSocket (Twilio Media Stream)
  • Socket.IO

Voice / AI

  • Twilio (Call, TwiML, Media Stream)
  • Azure Speech-to-Text (Streaming)
  • Gemini (대화 추천 멘트, 통화 요약)
  • ElevenLabs (TTS)

Database / Infra

  • PostgreSQL
  • Prisma ORM
  • Docker / Docker Compose
  • ngrok

핵심 기능

전화 발신 및 통화 흐름 제어

  • 웹에서 발신 요청 시 Twilio Call API로 전화 연결
  • TwiML을 통해 통화 시작, 대기, 음성 재생 등 통화 흐름 제어
  • 통화 상태 콜백을 수신해 실시간 상태 동기화

실시간 음성 인식 및 자막 제공

  • Twilio Media Stream을 WebSocket으로 수신
  • μ-law 음성을 PCM으로 변환 후 Azure STT로 실시간 음성 인식
  • 인식 결과를 자막 형태로 프론트에 실시간 전달

AI 기반 대화 지원

  • STT 결과를 기반으로 Gemini가 상황별 추천 답변 생성
  • 추천 멘트를 프론트에 실시간 제공하여 전화 응대 보조

TTS 생성 및 재생

  • 추천 멘트 또는 사용자 입력 문장을 ElevenLabs TTS로 음성 생성
  • 생성된 음성을 통화 중 실시간 재생하거나 웹에서 미리듣기 지원

통화 요약 및 기록 관리

  • 통화 종료 시 전체 대화를 Gemini로 요약
  • 요약 및 대화 기록을 DB에 저장하고 조회 가능

실시간 음성 처리 아키텍처

  1. 웹에서 전화 발신 요청
  2. Twilio가 전화를 연결하고 Media Stream으로 음성 송출
  3. 서버가 음성을 WebSocket으로 수신 후 STT 수행
  4. 인식된 텍스트로 추천 멘트 생성 및 실시간 전달
  5. 선택된 멘트를 TTS로 생성해 통화 중 재생
  6. 통화 종료 후 요약 생성 및 저장

파이프라인 요약:
Twilio Media Stream → WebSocket → Azure STT → Gemini → Socket.IO → (ElevenLabs TTS)

ERD

프로젝트 구조

전체 디렉터리 구조
front/ # Node(Express) 서버 + 정적 프론트(바닐라 JS) 통합 디렉터리
├─ public/ # 프론트 정적 리소스(HTML/CSS/JS)
├─ src/ # 서버 핵심 로직
│ ├─ routes/ # HTTP API 라우트(auth/user/call/twilio)
│ ├─ sockets/ # 실시간 처리(WebSocket: Media Stream, Socket.IO)
│ ├─ services/ # 외부 연동 로직(Twilio, ElevenLabs, Gemini 등)
│ ├─ config/ # 환경변수 및 외부 클라이언트 초기화
│ ├─ auth/ # Google OAuth 설정
│ ├─ db/ # Prisma 클라이언트
│ └─ utils/ # 오디오 유틸(μ-law→PCM 변환 등)
├─ audio/ # TTS로 생성된 mp3 파일 정적 서빙
├─ prisma/ # Prisma 스키마/마이그레이션
├─ server.js # 서버 엔트리
├─ docker-compose.yml # 로컬 Postgres 실행
└─ .env.example # 환경변수 템플릿
디렉터리 구조 상세
front/
audio/ # TTS로 생성된 오디오(mp3) 정적 서빙 디렉터리
node_modules/ # 의존성
prisma/ # Prisma schema 및 migration 관리
public/ # 프론트 정적 리소스
css/ # 공통 스타일
javascript/ # 브라우저 스크립트
pages/ # HTML 페이지
src/ # 서버 핵심 로직
auth/
passport.js # Google OAuth 전략/세션 설정
config/
env.js # 환경변수 로딩, 외부 API 클라이언트 초기화
voice.js # 음성 프리셋 매핑/관리
db/
prisma.js # Prisma Client 초기화 및 DB 연결
routes/
authRoutes.js # 로그인, 콜백, 로그아웃 등
callRoutes.js # 통화 생성, 상태 관리, 미리듣기
twilioRoutes.js # TwiML 응답 및 Twilio 콜백
userRoutes.js # 프로필 조회, 보이스 설정
services/
ttsService.js # ElevenLabs TTS 호출, 오디오 파일 생성/저장
twilioService.js # Twilio Call 생성/업데이트/제어
summaryService.js # 통화 요약 (Gemini)
sockets/
mediaSocket.js # Twilio Media Stream WebSocket 수신 → STT(Azure) → 추천(Gemini) 파이프라인
frontendSocket.js # 프론트로 실시간 이벤트 Push(자막/추천멘트/통화 상태)
utils/
audio.js # 오디오 파일 경로/입출력 유틸
mulaw.js # 오디오 전처리: μ-law → PCM 변환
.env
.env.example # 환경변수 템플릿
.gitignore
docker-compose.yml # 로컬 Postgres 실행 설정
package-lock.json
prisma.config.ts
README.md
server.js # 서버 엔트리: Express 설정 + 라우트/소켓 초기화

실행 방법

1️⃣ 사전 준비

  • Node.js (권장: LTS)
  • Docker / Docker Compose
  • ngrok
  • 계정 및 API Key: Twilio, Azure STT, ElevenLabs, Gemini API

2️⃣ 환경변수 설정

프로젝트 루트(front/)에 .env 파일을 생성하고 아래 값을 설정합니다.
필수 값은 .env.example 파일을 참고하세요.

환경변수 목록 보기
# Server
PORT=3003
PUBLIC_HOST=
SERVER_URL=
# Twilio
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_API_KEY=
TWILIO_API_SECRET=
TWILIO_TWIML_APP_SID=
TWILIO_FROM_NUMBER=
TWILIO_CALLER_ID=
TWILIO_MEDIA_STREAM_URL= # (선택) 기본적으로 PUBLIC_HOST 기반 동작# STT / LLM
AZURE_SPEECH_KEY=
AZURE_SPEECH_REGION=
GEMINI_API_KEY=
# ElevenLabs
ELEVENLABS_API_KEY=
ELEVENLABS_MODEL_ID=
ELEVENLABS_VOICE_ID=
# ElevenLabs Voice Presets
ELEVENLABS_VOICE_ID_BRIGHT=
ELEVENLABS_VOICE_ID_CLEAR=
ELEVENLABS_VOICE_ID_CALM=
ELEVENLABS_VOICE_ID_WARM=
# Database
DATABASE_URL=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
POSTGRES_PORT=
POSTGRES_HOST=
# Auth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
SESSION_SECRET=

3️⃣ 서버 실행

npm install
docker-compose up -d # Postgres 실행
node server.js # 서버 실행

4️⃣ 외부 접근 설정 (ngrok)

Twilio Webhook 및 Media Stream 연결을 위해 ngrok으로 서버를 외부에 노출합니다. ngrok에서 발급된 URL을 PUBLIC_HOST 환경변수에 설정합니다.

ngrok http 3003

주요 엔드포인트

구분MethodPath설명
ClientPOST/calls전화 발신 요청 (시작 멘트 생성 후 Twilio 발신)
ClientGET/calls/history통화 기록 조회
ClientPOST/tts-previewTTS 미리듣기
TwilioPOST/call-status통화 상태 콜백
TwilioALL/twilio/answerTwiML 응답 (스트림 시작 + 시작 멘트 재생)
TwilioALL/twilio/holdTwiML 응답 (대기 유지 + 스트림 유지)

기술적 의사결정

Media Stream 기반 실시간 음성 처리

HTTP 업로드·폴링 방식 대신 Twilio Media Streams(WebSocket) 구조를 적용해,
통화 음성을 프레임 단위로 수신하며 STT 지연을 최소화했습니다.

  • HTTP 기반 업로드 방식은 음성 버퍼링 및 전송 지연 발생
  • WebSocket 스트리밍은 통화 흐름을 유지한 채 즉시 음성 처리 가능

구현 핵심

  • TwiML <Start><Stream />을 사용해 통화 오디오를 WebSocket으로 스트리밍
  • 서버에서 httpServer.upgrade를 통해 WebSocket 연결 수립
  • start / media / stop 이벤트 단위로 오디오 프레임 수신
  • μ-law 오디오를 PCM으로 변환 후 Azure STT 스트림 입력으로 전달

STT 결과 실시간 Push 구조

STT 인식 결과를 Socket 기반으로 즉시 프론트에 전달해 응답 지연을 최소화했습니다.
통화 중 실시간 자막 제공과 후속 응답 처리가 가능하도록 설계했습니다.

  • Polling 방식은 네트워크 오버헤드와 응답 지연 발생
  • Push 방식은 인식 완료 즉시 사용자에게 피드백 제공 가능

구현 핵심

  • Azure Speech SDK의 스트리밍 인식 방식 사용
  • STT 결과 수신 즉시 Socket 이벤트로 프론트엔드에 전달
  • 최근 인식 텍스트 + 시간 기준 중복 필터링 로직으로 반복 인식 방지

프론트엔드 연계

  • callSid 기준으로 Socket room 연결
  • STT 결과 수신 즉시 자막 표시
  • 인식 텍스트 기반 응답 추천 트리거 실행

서비스 단위 분리 설계 (Routes ↔ Services)

routes는 I/O 처리만 담당하고, services에 비즈니스 로직을 집중시켜 확장성과 유지보수성을 강화했습니다.
외부 벤더 의존성을 services 계층에 캡슐화해, STT·TTS·LLM 교체 시 코드 변경 범위를 최소화하도록 설계했습니다.

설계 원칙

  • routes
    • HTTP / Socket 이벤트 수신
    • 요청·응답 처리만 담당
  • services
    • 핵심 비즈니스 로직
    • 외부 API(STT, TTS, LLM, Twilio) 호출 담당
  • 벤더 교체 및 기능 확장 시 services 계층만 수정하도록 구조화

핵심 구현 예시

  • TTS
    • src/services/ttsService.js
    • synthesizeToFile()
  • 통화 제어
    • src/services/twilioService.js
    • playToCall()
  • 요약 처리
    • src/services/summaryService.js
    • summarizeCall()

트러블슈팅

OAuth 로그인 후 로그인 전 화면으로 되돌아가는 이슈

  • 원인: 서버는 세션 기반 인증(Passport)을 사용했으나, 프론트는 localStorage userId 기준으로 로그인 상태를 판단해 인증 기준이 불일치
  • 해결: 로그인 성공 후 /api/me를 통해 세션 사용자 정보를 조회하고, 프론트 상태를 세션 기준으로 재동기화하여 단일 인증 흐름으로 통합

사용자별 보이스 설정 구조를 단일 결정 흐름으로 개선

  • 원인: User 테이블에 보이스 정보를 포함할 경우 우선순위 관리가 어려움.
  • 해결: 보이스 설정을 UserVoiceSetting으로 분리하고, 프리셋 → 사용자 설정(DB) → 기본값 순으로 결정 로직을 명시

실시간 WebSocket 연결 끊김 및 재연결 이슈

  • 원인: 프론트–서버 WebSocket 연결이 끊길 경우, 소켓에 바인딩된 callSid / userId 컨텍스트가 유실되어 통화 중 이벤트(TTS 재생, 추천 멘트)가 정상 처리되지 않음
  • 해결: 소켓 연결 시 bind.call 이벤트로 통화 컨텍스트를 명시적으로 바인딩하고, 이벤트 처리 시 바인딩 여부를 검증하여 재연결 후에도 동일한 통화 단위(callSid) 기준으로 상태를 재동기화하도록 설계

Twilio 콜백 기반 통화 상태 불일치 이슈

  • 원인: 통화 종료/실패 이벤트가 Twilio 콜백, 사용자 종료 요청, WebSocket 이벤트 등 여러 경로로 분산되어 통화 상태 관리가 일관되지 않음
  • 해결: 통화 상태 변경을 Twilio statusCallback과 서버 콜백(/call-status)을 중심으로 단일화하고, 통화 종료 시 Socket Room(callSid) 기준으로 이벤트를 전파하여 클라이언트 상태를 일관되게 동기화

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages