Repository navigation
리뉴얼: Streamlit+RunPod를 로그인 전용 서버리스 웹앱(Vercel·Modal·Supabase)으로 - #1
Merged
Merged
Conversation
서버리스 웹앱 리뉴얼(docs/specs/renewal_serverless.md)로 쓰이지 않게 된 것을 지운다. - Streamlit 앱: src/app.py, src/ui/, ui/, .streamlit/, run_local.sh - 배포: Dockerfile, docker/, .dockerignore, docker-build 워크플로, docs/runpod_deploy.md, landing/ - 오케스트레이션: gpu_worker_pool.py, speaker_verify.py, auth.py (계정은 Supabase Auth로 대체) - 위 코드만 검사하던 테스트: test_ui, test_theme, test_my_recipes, test_speaker_verify, test_auth 남은 코드(orchestration 핵심, stt, tts, llm)와 테스트 87개는 그대로 통과한다.
- server/chefear_api: 기존 Streamlit 흐름을 옮긴 대화 상태 기계(상태는 Supabase sessions), Supabase 토큰 확인, 음성 주소 결정, 마이레시피 REST - 서비스 전체 로그인 전용: 비로그인은 health·warmup 외 401 login_required, warmup은 비로그인이면 GPU를 깨우지 않음 - guard.py: 모든 사용자 글자를 정리·길이 제한·공격 문구 차단. 동작 이름·ID 형식·JSON 본문·요청 크기 검증, 보안 헤더 - inference/modal_app.py: api(CPU), Stt·Tts(L4, 안 쓰면 0대, 모델은 이미지에 포함), RecipeAudio(동시 1대) - db/migrate_renewal.sql: sessions·tts_assets·tts_jobs, 버킷 2개, 공개(anon) 키 접근 정책 제거와 RLS - scripts: 공용 음성 목록 추출·업로드, data/tts_manifest.csv(2,724문장) - STT 후처리를 src/stt/postprocess.py로 분리(동작 변경 없음) - tests/renewal: 79개(API 계약, 상태 기계, 입력 방어, 추론 로직, 음성 목록)
- 로그인 화면(구글 우선, 이메일은 시트), 로그인 후 시작하기 - 브라우저 VAD(silero)로 발화를 잘라 WAV로 보냄. 끄기·로그아웃·등록 화면에서는 마이크 연결을 닫음 - 원본 로고·배경·색·칩·점 진행·파형 플레이어·마이크 바·대화 카드, rem 기반 반응형 - 화면 단위로 DOM을 통째로 교체해 잔상 방지, 처리 중 흐림·클릭 차단 - 레시피 등록 완료 후 음성 준비 안내(보통 1~3분)와 완료 표시 - 서버 오류 코드를 한글 문구로, 입력칸 길이 제한 - VITE_MOCK=1 데모 모드(가짜 API)
- README를 새 구조로 다시 씀(평가·팀 기록은 유지), 한계와 미측정 항목 명시 - docs/specs/renewal_serverless.md 추가, 스펙 목록·결정기록·AGENTS.md·테스트 문서 갱신, 리뉴얼 전 문서에 안내 배너 - renewal/DEPLOY.md, API_CONTRACT.md(로그인 전용, 입력 검증, 오류 코드) - .env.example, requirements 정리(Streamlit·관리자 의존성 제거), .gitignore(data/tts_audio 등) - CI(.github/workflows/ci.yml: pytest + 웹 빌드), keepalive.yml(Supabase 정지 방지) - 새 화면 캡처 docs/images/renewal
읽어 줄 문장을 정하는 _say()가 대화 기록에 ai 줄을 남기지 않아 카드에 나만 보이던 것을 원본처럼 고친다(끝의 문장부호는 제거). 테스트 1개 추가(167개).
- 말을 보낸 뒤 0.4초 넘게 걸리면 오버레이(말씀 잘 들었어요, 잠시만요...)와 880Hz 알림음 1회. 글자·버튼 요청은 기존처럼 화면만 흐림 - 우상단은 아이디(이메일 9자까지, 말줄임) 버튼 하나로 바꾸고, 누르면 마이레시피, 로그아웃은 마이레시피 안으로(원본과 같게) - 마이레시피 제목에 아이디님이 등록한 레시피 형태로 표시
로그인부터 마이레시피 수정까지 데모 모드에서 새로 찍음(우상단 아이디 표시, 음성 준비 안내 포함).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
요약
Streamlit + RunPod 상시 GPU 구조를 로그인 전용 서버리스 웹앱(웹 Vercel + API·STT·TTS Modal + DB·파일·로그인 Supabase)으로 다시 구성합니다. 파인튜닝한 STT·TTS 모델, 500개 레시피 데이터, 대화 흐름(상태 기계)은 그대로 두고 서빙과 연결 방식만 바꿨습니다. 안 쓸 때는 API·GPU가 0대(마지막 요청 5분 뒤)가 됩니다.
배포본: https://chefear.vercel.app (로그인 필요). 구조·결정·인수 기준은
docs/specs/renewal_serverless.md, 배포 절차는renewal/DEPLOY.md.왜
변경 내용 (커밋 4개로 나눔)
refactor제거src/app.py,src/ui/,ui/), RunPod·Docker·GHCR 배포, 소개 페이지(landing/), GPU 워커 풀, 관리자 화자검증, 로컬 계정 모듈과 이들만 검사하던 테스트feat(server)server/chefear_api(FastAPI, 상태 기계·로그인 확인·마이레시피),inference/modal_app.py(Stt·Tts는 L4, 등록 음성은 동시 1대),db/migrate_renewal.sql, 공용 음성 업로드 스크립트, 입력 방어guard.py, 테스트 79개feat(web)VITE_MOCK=1)docs.env.example·requirements 정리, CI, keepalive핵심 동작:
health·warmup외 모든 요청이 401login_required.warmup은 비로그인이면 GPU를 깨우지 않습니다.sessions: API가 꺼졌다 켜져도 이어집니다.검증한 것
pytest tests167개 통과(기존 87 + 신규 80). 깨끗한 가상환경(CPU torch +requirements-server.txt)에서 CI와 같은 명령으로 166개 통과(그 뒤 ChefEar 줄 테스트 1개 추가).npm run build(타입 검사 포함) 통과.검증하지 못한 것 / 위험
RecipeAudio)은 실제 Modal에서 한 번도 실행하지 않았습니다.리뷰에서 봐 주실 결정
src/llm에 남겨 A/B용으로 둠). 정확도 비교가 필요하면 별도 작업입니다.users테이블 10명은 새 로그인과 연결되지 않고 데이터만 남습니다.approved='N'행을 처리하는 용도였는데, 그런 행이 DB에 남아 있는지는 확인하지 않았습니다.db/migrate_renewal.sql을 이미 적용했습니다(새 테이블 3개, 버킷 2개, 공개 키 접근 정책 제거, RLS). 서버 전용 키를 쓰는 기존 앱에는 영향이 없지만, 공개 키로 접속하던 코드가 있다면 막힙니다.머지 후 할 일
SUPABASE_URL,SUPABASE_KEY등록(keepalive.yml이 3일마다 접속해 Supabase 무료 프로젝트 정지를 막음, 기본 브랜치에 있어야 동작).python scripts/upload_tts_audio.py).chefear.store)는 이 PR로 바뀌지 않으므로 필요하면 따로 정리.되돌리기
PR을 되돌려도 DB 마이그레이션(공개 키 접근 정책 삭제, RLS)은 되돌려지지 않습니다. 이전 Streamlit 앱을 다시 쓰려면 서버 전용 키를 써야 합니다.
구현·검증은 Claude Code로 진행했고, 사용자의 요청에 따라 커밋·PR을 만들었습니다(
AGENTS.md의 "에이전트는 커밋하지 않는다" 규칙의 예외).