尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证외국인 여행자를 위한 포장마차 메뉴판 식재료·할랄·알레르기 확인 모바일 웹 (Next.js + Gemini)
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗한국 지역 축제 포장마차에서 외국인 여행자가 "이거 먹어도 되나?"를 바로 확인하는 모바일 웹
메뉴판 사진을 찍으면 AI(Google Gemini)가 메뉴를 읽고, 육수·젓갈·양념·튀김 기름 같은 숨은 재료까지 추정해 내 식이 기준(할랄, 채식, 알레르기 등)에 맞춰 메뉴마다 OK / ASK / AVOID를 알려줍니다. 애매한 메뉴는 상인에게 보여줄 한국어 질문을 큰 글씨로 띄우고, 상인이 화면의 버튼으로 답하면 그 답을 내 언어로 보여줍니다.
⚠️ Reference only — not a halal/vegan/allergy certification. Recipes vary by stall.
| 기능 | 설명 |
|---|---|
| 📷 메뉴판 사진 분석 | 후면 카메라 바로 실행 → 메뉴를 읽고 숨은 재료까지 추정 → 기준별 OK / ASK / AVOID + 확신도 + 이유 |
| 🗣️ 상인에게 묻기 | 인사말 + 내 제한(한국어 고정 문장) + 질문을 전체 화면 큰 글씨로. 상인이 "네, 들어가요 / 아니요 / 잘 모르겠어요" 버튼을 누르면 내 언어로 결과와 조언 표시 |
| 🪪 한국어 식사 카드 | 사진·AI·네트워크 없이 바로 쓰는 카드. "할랄"을 모르는 상인도 알 수 있게 풀어 씀("돼지고기·돼지기름·돼지뼈 육수와 술(맛술·미림 포함)이 들어가면 못 먹어요.") |
| 🔊 한국어로 읽어주기 (ElevenLabs) | 상인 화면·식사 카드·SOS를 ElevenLabs의 자연스러운 한국어 음성으로 읽어줌(시끄러운 축제에서 글씨가 안 보일 때). 오프라인·실패 시 폰 기본 음성으로 자동 전환. 보여주는 동안 화면 꺼짐 방지 |
| 🚨 SOS 응급 카드 | 알레르기 반응 시 주변 사람에게 보여줄 한국어 응급 문장 + 119 바로 걸기 + 읽어주기 |
| 🥗 18가지 기준 + 직접 입력 | Diet: 할랄, 채식, 비건, 돼지고기, 소고기 · Allergies: 갑각류, 연체류(오징어·조개·굴), 생선, 달걀, 유제품, 밀, 땅콩·견과류, 대두, 참깨 · 더 보기: 메밀, 복숭아, 토마토, 아황산류 · 그 밖에("마늘, MSG" 등 자유 입력 → 상인 화면에 한국어로) |
| 🌏 5개 언어 | English · 日本語 · 简体中文 · 繁體中文 · العربية(RTL). 재료는 "egg (달걀)"처럼 내 언어 + 한국어 |
| 🔁 대신 먹을 메뉴 | AVOID 카드에 같은 메뉴판에서 OK인 메뉴를 바로 추천 |
| 📶 오프라인 | 홈 화면에 추가(PWA)하면 식사 카드·SOS는 인터넷 없이도 열림 |
| 🏷️ 식품 라벨 모드 | 편의점 식품 뒷면 원재료를 그대로 읽고 "…함유" / "같은 제조시설" 문구를 따로 표시 |
| ✨ 샘플 데모 | API 호출 없이 즉시 결과를 보여주는 시연용 안전망 |
| 🔎 결과 정리 | 요약("6 items: 2 OK · 2 ASK · 2 AVOID"), 필터 탭, 위험 순/메뉴판 순 정렬, 이미지로 저장·공유 |
| 💾 기준 기억 | 고른 기준·언어를 휴대폰에 저장해 다음 가게에서 바로 촬영 |
| 기준 | 대응 |
|---|---|
| 열림 | URL만 열면 바로 사용, 로그인/가입 없음. 첫 화면에 설명·기준·촬영 버튼이 스크롤 없이 보임(375×812 기준). 네트워크·AI가 안 될 때도 샘플 데모와 식사 카드로 사용 가능. 5개 언어 + RTL, 다크 모드 폰에서도 야외 가독성을 위해 항상 밝은 고대비 화면 |
| 문제 발견 | 번역으로는 알 수 없는 숨은 재료(육수, 젓갈, 맛술, 돼지기름, 공유 기름)와, 상인과의 질문·답이 통하지 않는 순간을 해결 |
| AI 활용 | 한 번의 비전 호출로 ① 간판·손글씨 메뉴 읽기 ② 숨은 재료 추정 ③ 기준별 판정 + 확신도 ④ 메뉴별·메뉴판 전체 한국어 질문 생성 ⑤ 사용자 언어로 이유·질문 번역. JSON 스키마 구조화 출력 + zod 검증, 실패 시 가벼운 모델로 재시도. 상인에게 들려줄 한국어 문장은 ElevenLabs 음성 합성(eleven_flash_v2_5)으로 읽음 |
| 차별성 | 번역 → 재료 추정 → 판정 → 상인 질문 → 상인 답을 다시 내 언어로까지 한 흐름. 보수적 판정(의심되면 ASK), 인증·"안전" 표현 금지 |
Next.js (App Router) · TypeScript · Tailwind CSS · @google/genai (Gemini) · ElevenLabs Text-to-Speech · zod · Vercel
npm install
cp .env.example .env.local # MOCK_MODE=true: 키 없이 전체 UI 흐름 동작
npm run dev # http://localhost:3000
실제 AI 호출: .env.local에서 MOCK_MODE=false, GEMINI_API_KEY=... 입력 후 재시작. 키는 Google AI Studio에서 무료 발급(무료 티어는 호출 횟수 제한 있음).
| 이름 | 필수 | 설명 |
|---|---|---|
GEMINI_API_KEY | 실제 호출 시 | 서버(API route)에서만 사용. NEXT_PUBLIC_ 붙이지 말 것. 없으면 server_misconfigured(500) |
GEMINI_MODEL | 아니오 | 기본값 gemini-3.6-flash. (gemini-3.8-flash는 2026-10-27 기준 무료 티어 과부하가 잦아 제외) |
GEMINI_FALLBACK_MODEL | 아니오 | 기본값 gemini-3.5-flash-lite. 첫 시도 실패(과부하 503 등) 시 재시도에 사용 |
ELEVENLABS_API_KEY | 아니오 | 읽어주기 음성용. 서버에서만 사용. 없으면 /api/tts가 501을 주고 폰 기본 음성으로 읽음 |
MOCK_MODE | 아니오 | true면 샘플 JSON 반환 (AI 호출 안 함) |
.env.local은 .gitignore에 포함. 실제 키는 커밋하지 말 것.
app/page.tsx 첫 화면 · 결과(요약/필터/정렬/카드) · 상인용 화면(답 버튼) · 식사 카드
app/api/analyze/route.ts 검증 · 요청 제한 · MOCK_MODE · Gemini 호출(55초 예산, 실패 시 fallback 모델로 재시도)
app/api/tts/route.ts ElevenLabs 한국어 음성(텍스트 검증 · IP당 분당 20회 · 8초 제한, 키·문장은 로그에 안 남김)
lib/gemini.ts Gemini 구조화 출력 호출
lib/schema.ts 요청/응답 zod 스키마, 기준·언어·모드 화이트리스트
lib/prompt.ts 메뉴판/라벨 시스템 프롬프트(숨은 재료·할랄 규칙), 언어별 예시
lib/i18n.ts UI 문자열 5개 언어 + 상인용 한국어 고정 문장(VENDOR_KO)
lib/lang-check.ts AI 답이 다른 언어·한글이 섞여 오면 한 번 재시도
app/manifest.ts, public/sw.js 홈 화면 추가(PWA) · 오프라인 캐시(/api는 캐시 안 함)
lib/image.ts 브라우저 리사이즈/압축 (긴 변 1600px, JPEG 0.8→필요시 낮춤, ≤3MB)
lib/sample.ts 샘플 데모 결과 (MOCK_MODE도 이것을 반환) + public/sample-menu.svg
lib/share.ts 결과를 캔버스 이미지로 그려 공유/다운로드
tests/menu-cases.json 대표 메뉴 30개 × 기준별 기대 판정
tests/eval.mts 프롬프트 자동 채점 스크립트
tests/ask.test.mts 상인 질문 템플릿·조사 검사 (node tests/ask.test.mts)
tests/lang-check.test.mts 언어 섞임 검사 (node tests/lang-check.test.mts)
lib/ask.ts 상인 질문을 고정 틀로 생성("…들어가나요?" → 네 = 들어감 보장)
maxDuration = 60.| 상황 | API | 화면 안내 | 다시 시도 |
|---|---|---|---|
| 메뉴판을 못 읽음 / 가격만 있음 / 음식 사진 | 200 readable:false | We couldn't read it… (+ 이유) | ✓ |
| 스키마 불일치 | 재시도 후 502 analysis_failed | Something went wrong… | ✓ |
| Gemini 과부하 | 재시도 후 503 busy | The AI is busy… or show your diet card | ✓ |
| 응답 시간 초과 | 504 timeout | This took too long… | ✓ |
| 이미지 과대 / 형식 오류 | 413 too_large / 400 bad_image | That photo is too large… / We couldn't open… | 다른 사진 |
| 요청 제한 초과 | 429 rate_limited | Too many requests… | ✓ |
| 키 누락 / 잘못된 키 | 500 server_misconfigured | The service isn't set up yet… | 샘플로 |
| 네트워크 끊김 | — | Network error… | ✓ |
배포본 실제 호출 확인 (2026-10-27) — 샘플 메뉴판 이미지, 할랄 + 갑각류 알레르기
| 언어 | 결과 | 시간 |
|---|---|---|
| English | 순대·새우튀김 AVOID / 어묵·떡볶이 ASK / 호떡·고구마 OK, 맛술을 할랄 위험으로 탐지 | 6~8초 (한 번은 51초) |
| 日本語 | 같은 판정, 이름·재료·질문 일본어 ("豚の腸 (돼지 창자)") | 7.6초 |
| العربية | 같은 판정, 이름·재료·질문 아랍어 | 7.1초 |
프롬프트 안전성 채점 (실제 API 호출, 기준당 1회):
GEMINI_API_KEY=... npm run eval # 기존 9개 기준
GEMINI_API_KEY=... npm run eval -- pork # 한 기준만
메뉴 30개를 텍스트로 보내 기준별 판정을 채점하고 tests/eval-results.md에 표와 목록을 씀. 핵심 지표는 정확도가 아니라 위험한 오판(재료가 들어가거나 들어갈 수 있는데 OK라고 한 것)의 수. 더 조심스럽게 판정한 것(AVOID 대신 ASK)은 의도된 동작, OK여도 되는데 AVOID한 것은 "오경보"(불편하지만 위험하진 않음)로 따로 셈. 기대값은 일반적인 노점 레시피 기준으로 사람이 쓴 것이라 정답이 아님. 할랄·채식·대두·참깨는 기대값이 아직 없음.
1차 실행(2026-10-28, 6개 기준·판정 180개)에서 위험한 오판 25건. 원인을 나눠 보니 실제 AI 오류(참고표에 적힌 돼지뼈 육수·라드를 무시, 감자전·빈대떡의 달걀 등)와 근거 없이 엄격했던 기대값이 섞여 있었음 → 프롬프트에 "참고표에 있는 숨은 재료가 제한에 걸리면 최소 ASK" 규칙을 넣고 근거 없는 기대값 12개를 정리함. 수정 후 재채점은 아직 안 함.
수동 확인: MOCK_MODE=true로 실행 → 기준 선택 → 아무 사진 업로드 → 결과/필터/정렬/상인 화면 답 버튼/식사 카드/공유 확인. 언어를 العربية로 바꿔 RTL 확인.
GEMINI_API_KEY, ELEVENLABS_API_KEY, MOCK_MODE=falsemaster에 push하면 자동 배포. 환경변수를 바꾸면 Redeploy 필요