dev.to

I built an AI that only works when my phone is in my pocket

휴대전화를 주머니에 넣어야 제대로 작동하는 AI 산책 앱을 만들었습니다

Drift는 산책 전에 브라우저에서 AI가 만든 안내를 음성 파일로 준비해, 휴대전화를 잠근 뒤에도 오프라인으로 들려주는 앱입니다. Gemma·Kokoro·Whisper를 기기에서 실행하며 화면을 켠 시간을 줄이는 데 초점을 맞췄습니다.

AI 요약

Drift는 목적지나 경로 대신 주변을 새롭게 살펴보도록 안내하는 산책 앱입니다. 사용자는 산책 시간과 ‘조용함’, ‘색’, ‘오래된 것’, ‘소리’ 같은 분위기를 고릅니다. 브라우저에서 실행하는 Gemma 4가 짧은 규칙을 만들고, 음성 모델 Kokoro가 각 규칙을 읽습니다. 산책이 끝나면 사용자가 눈여겨본 것을 말로 남기고, Whisper가 기기 안에서 받아 적습니다. Gemma는 이를 현장 노트로 정리하며, 노트에 남긴 기억 한 줄은 다음 산책의 방향에 반영됩니다.

화면 대신 오디오를 미리 준비합니다

개발자는 휴대전화를 잠그면 웹페이지의 타이머와 백그라운드 GPS가 멈추는 문제를 만났습니다. 규칙을 정해진 시간마다 재생하는 방식은 화면이 잠긴 뒤 작동하지 않았습니다. 대신 산책을 시작하기 전에 Gemma가 규칙을 만들고 Kokoro가 음성으로 변환합니다. 각 규칙 앞에는 짧은 종소리를 넣고, 규칙 사이에는 산책할 시간만큼 무음을 이어 붙여 하나의 WAV 파일로 만듭니다. 사용자는 재생을 시작한 뒤 휴대전화를 잠그면 됩니다. 30분 분량도 단일 오디오 파일로 준비하며, 산책 중 네트워크나 GPS, 화면을 깨우는 작업은 필요하지 않습니다.

앱은 Vite와 TypeScript로 만든 정적 웹 앱이며 서버 백엔드가 없습니다. Gemma 4 E2B는 MediaPipe LLM Inference와 WebGPU로 규칙과 노트를 작성합니다. Kokoro-82M은 kokoro-js로 음성을 만들고, Whisper base는 transformers.js로 사용자의 말을 받아 씁니다. 모델은 모두 기기에서 실행합니다. 첫 방문에는 모델 약 2.5GB를 내려받아야 하며, 개발자의 측정에서 Gemma 4가 산책 규칙을 쓰는 데 11.5초, 노트 작성에는 2.2초가 걸렸습니다. 산책 도중에는 네트워크가 필요하지 않습니다. 글에서는 Chrome·Edge 또는 Safari 26 이상 등 WebGPU를 지원하는 환경을 안내합니다.

화면 사용 시간을 기록합니다

Drift가 산책 중 확인하는 수치는 화면이 켜져 있던 시간입니다. Page Visibility API를 이용해 페이지가 보이는 동안 초 단위로 누적합니다. 개발자가 iPhone Safari에서 시험한 첫 산책은 실제로 약 10분 걸었지만, 앱이 센 시간은 2분이었습니다. 산책을 일찍 종료한 뒤에도 계속 걸었기 때문입니다. 화면 켜짐 시간은 2분 5초였습니다. 개발자는 휴대전화를 손에 든 채 화면을 잠그지 않아, 앱이 세려던 시간을 정확히 기록했다고 설명합니다. 앱의 목적은 사용자가 산책 중 화면을 보지 않도록 하는 데 있습니다.

산책 뒤 Whisper가 실제로는 거의 들리지 않는 녹음에 “Thank you.”라고 기록한 일도 있었습니다. 개발자는 이를 Whisper가 침묵을 자막에서 자주 본 표현으로 잘못 채우는 문제라고 설명합니다. 이제 녹음 길이나 음량이 너무 짧거나 작으면 받아쓰기를 중단하고 다시 녹음하도록 요청합니다. 해당 동작을 실제 Whisper 모델로 시험하는 테스트도 추가했습니다.

실제 모델을 돌리는 테스트에서 발견한 문제

Playwright 종단 간 테스트는 실제 Gemma와 Kokoro를 실행하고, 녹음된 음성을 가짜 마이크로 재생해 Whisper까지 확인합니다. 테스트 과정에서 Gemma 4가 빈 문자열을 반환하는 문제가 드러났습니다. MediaPipe는 프롬프트를 그대로 모델에 전달했는데, Gemma 4는 정해진 채팅 템플릿 안에서만 답변했습니다. 템플릿을 붙인 뒤 문제가 해결됐고, 테스트는 결과 제목이 기본 제목으로 돌아가지 않았는지 확인합니다.

또한 일부 브라우저는 사이트 저장 공간을 2GB보다 작게 제한합니다. 기존에는 cache.put()이 실패해도 조용히 넘어가 Gemma를 방문할 때마다 다시 내려받았습니다. 이제 navigator.storage.estimate()로 저장 공간을 먼저 확인하고, 모델을 오프라인에 보관하지 못할 상황을 사용자에게 알립니다. Gemma가 요청한 산책 시간과 규칙별 시간을 맞추지 못하면 무음 구간 길이를 다시 조정해 전체 시간을 맞춥니다. 예시 문장을 그대로 복사하는 경향을 줄이도록 프롬프트도 바꿨습니다.

노트 작성에서도 사실을 덧붙이거나 받아쓰기를 그대로 복사하는 문제가 있었습니다. 예를 들어 사용자가 자전거와 빵집, 나무와 바람을 말했다고 녹음했는데, 작은 노트용 모델이 말하지 않은 벽돌의 촉감을 노트에 넣었습니다. 반대로 Gemma 4는 처음에 녹취를 거의 그대로 복사했습니다. 개발자는 자연 관찰자의 문체로 다시 쓰되, 말하지 않은 장면을 지어내지 말도록 프롬프트를 조정했습니다. 테스트는 실제 녹음 예시와 모델을 이용해 이런 오류를 확인합니다.

로컬 실행을 택한 이유

개발자는 오픈 웨이트 모델을 택한 이유로 오프라인 사용, 음성 데이터의 사생활 보호, 운영 비용을 듭니다. 산책 규칙은 출발 전에 기기에서 만들기 때문에 통신이 닿지 않는 곳에서도 작동합니다. 산책 뒤 녹음도 서버로 전송하지 않으며, 계정이나 API 키, 데이터베이스를 두지 않습니다. 정적 사이트라 사용자가 늘어도 서버 추론 비용이 증가하지 않습니다. 모델을 바꾸거나 음성을 고르고, 문제가 생긴 계층의 코드를 직접 살펴볼 수 있다는 점도 설명합니다.

dev.to 반응

  • @ivannovazzi — 화면이 켜져 있던 시간을 기록한다는 발상이 좋습니다. 보통 산책 앱이라면 지도와 함께 연속 기록을 붙였을 텐데요. 사용자가 휴대전화를 주머니에 넣고 벤치에 앉아 있으면, 움직임이 멈춘 걸 알아차리나요?
    • @codeswithroh — 지금은 알아차리지 못합니다. 작동하는 추적 기능은 넣지 않았습니다. 이 앱은 자연과 주변 환경을 의식하게 하려고 만들었습니다. 벤치에 앉아 주변 자연을 의식하며 관찰하는 것도 멋진 일입니다.
  • @koda2026 — 브라우저를 잠근 상태에서도 작동하게 하려고 오디오 파일 하나에 무음을 이어 붙인 방식은 제약을 다루는 엔지니어링의 좋은 사례입니다. 대부분은 복잡한 백그라운드 위치 추적 API나 Wake Lock으로 운영체제와 씨름했을 겁니다. Whisper의 “Thank you.” 환각과 Gemma 4 채팅 템플릿 문제를 솔직하게 다룬 점도 좋습니다. 오픈 웨이트 로컬 추론은 소스를 읽고 버그를 추적해 프롬프트 계약을 고칠 수 있다는 점에서 블랙박스 API보다 낫습니다. 다만 오래된 기기나 느린 네트워크에서 2GB WebGPU 모델을 내려받지 못하면 어떻게 되나요? 작은 WASM 모델이나 텍스트만 제공하는 대체 경로가 있나요, 아니면 WebGPU가 필수인가요?
    • @codeswithroh — 오래된 기기에서 시험하다가 저도 같은 문제를 느꼈습니다. 더 작은 WASM 모델로 바로 작동하는 대체 경로를 넣었습니다. 2GB 부담을 없애도록 더 효율적으로 만드는 방안도 생각하고 있습니다.
    • @koda2026 — 그게 맞는 방향입니다. 2GB WebGPU 다운로드는 오래된 기기와 불안정한 네트워크 사용자에게 큰 장벽입니다. 가벼운 WASM 대체 경로가 있으면 기능을 조금 줄이더라도 앱의 기본 사용 흐름을 유지할 수 있습니다. 실제 기기에서 시험한 내용을 바탕으로 바로 개선하는 모습을 보는 게 좋습니다.
  • @ssapable — “타이머는 멈추지만 오디오는 계속 재생되니 떠나기 전에 작업을 다 한다”는 방식은 실제 출시를 시도해 본 사람에게서 나오는 아이디어입니다. 첫 산책에서 화면 켜짐 시간이 측정 내내 기록된 표도 솔직한 사용자 테스트입니다. 질문이 하나 있습니다. 각 기록에서 다음 산책을 위해 남기는 기억 한 줄은 다음 Prepare 프롬프트에 그대로 들어가나요, 아니면 Gemma가 먼저 요약하나요?
    • @codeswithroh — 영상이 정말 훌륭합니다. 봤는데 아주 좋았습니다. 만들어 주셔서 감사합니다. 그리고 Gemma가 먼저 요약합니다.
    • @ssapable — 그 말 들으니 저녁이 즐거워졌습니다. Gemma가 먼저 요약하는 게 맞습니다. 한 줄을 그대로 넣으면 다음 프롬프트가 이전 산책에 끌려갈 테니까요.
  • @ryanmoore — 기술을 이용해 휴대전화를 덜 쓰게 만드는 접근이 흥미롭습니다. 산책 전에 오디오 전체를 만들어 네트워크 없이 작동하게 한 점이 특히 좋습니다. 실제 시험과 그 과정에서 고장 난 사례도 프로젝트를 더 설득력 있게 합니다.
  • @sujit_kumar_83c1c8500570f — 저는 개발을 막 시작했는데, 게시물과 작업을 보고 정말 감탄했습니다. 앞으로도 기대하겠습니다.

원문: dev.to / 번역·요약: Trawling