Aplikasi kasir berbasis suara untuk pedagang pasar dan UMKM. Pengguna cukup menyebutkan nama barang dan harga, aplikasi akan menghasilkan nota secara otomatis.
Pedagang pasar tradisional dan pemilik warung sering kesulitan mencatat transaksi saat pembeli sedang ramai. Menulis manual maupun mengetik di HP sama-sama lambat dan rawan salah hitung. VoiceInvoice dibuat untuk menyelesaikan masalah ini dengan memanfaatkan AI multimodal, sehingga proses pencatatan cukup dilakukan lewat suara. Aplikasi ini dirancang sebagai PWA offline-first agar tetap dapat digunakan di area dengan koneksi internet yang tidak stabil.
┌─────────────────────────────────────┐
│ Browser / HP │
│ Next.js PWA - Mobile First │
│ IndexedDB (Dexie.js) - Offline │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ Next.js Route Handlers │
└──────┬──────────────────┬───────────┘
│ │
┌──────▼──────┐ ┌──────▼──────────────┐
│ Supabase │ │ Gemini 2.5 Flash │
│ PostgreSQL │ │ (audio -> JSON) │
│ + Auth │ └─────────────────────┘
└─────────────┘
- Next.js App Router: pengembangan fullstack dalam satu codebase, menyatukan UI mobile-first dan backend API
- Gemini 2.5 Flash: model multimodal yang dapat memproses audio secara langsung dan menghasilkan JSON terstruktur dalam satu request
- Supabase: menyediakan PostgreSQL dan autentikasi dalam satu platform tanpa perlu mengelola infrastruktur database secara terpisah
- Zustand + Dexie.js: Zustand untuk state management UI, Dexie.js untuk menyimpan transaksi ke IndexedDB saat offline dan melakukan sinkronisasi otomatis
- Tailwind CSS: styling mobile-first yang cepat dan konsisten
1. Multimodal langsung vs. pipeline dua langkah
Pendekatan awal yang dipertimbangkan adalah cascade approach: audio dikirim ke Whisper untuk transkripsi, kemudian teks dikirim ke model lain untuk parsing JSON. Pendekatan ini melibatkan dua request terpisah dengan dua titik kegagalan.
Setelah menguji Gemini 2.5 Flash, ditemukan bahwa model ini dapat menerima audio secara langsung dan mengembalikan transkripsi beserta JSON dalam satu request. Hasilnya adalah latensi yang lebih rendah dengan kode yang lebih sederhana. Trade-off yang diterima adalah ketergantungan pada satu provider, yang dinilai tidak menjadi masalah pada skala project ini.
2. Offline-first sebagai kebutuhan utama
Koneksi internet di area pasar tradisional sering tidak stabil, sehingga offline support bukan sekadar fitur tambahan. Setiap nota yang dibuat saat offline disimpan ke IndexedDB dengan status pending_sync, kemudian disinkronisasi ke Supabase secara otomatis ketika koneksi kembali tersedia.
git clone https://github.com/Fauza27/VoiceInvoice.git
cd VoiceInvoice
npm installSalin .env.example ke .env dan isi nilai berikut:
GEMINI_API_KEY=
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=Jalankan supabase_migration.sql di SQL Editor Supabase, kemudian:
npm run devBuka http://localhost:3000 dan izinkan akses mikrofon. Contoh input suara: "beras 5 kilo tujuh puluh ribu, gula 2 kilo dua puluh lima ribu".
Konsistensi output JSON pada input audio. Model terkadang menghasilkan format yang tidak konsisten, terutama jika kualitas audio kurang jelas. Penggunaan parameter responseMimeType: "application/json" terbukti meningkatkan stabilitas output secara signifikan.
Perbedaan perilaku MediaRecorder di mobile web. Chrome Android dan Safari iOS memiliki dukungan MIME type audio yang berbeda. Diperlukan penanganan fallback dari audio/webm;codecs=opus ke audio/webm, dan perbedaan ini hanya terdeteksi saat pengujian di perangkat fisik.
Kompleksitas sinkronisasi offline. Terdapat beberapa edge case yang perlu dipertimbangkan, seperti sinkronisasi parsial dan kemungkinan duplikasi data. Implementasi saat ini sudah memadai untuk kebutuhan project ini, namun untuk skala yang lebih besar akan diperlukan mekanisme conflict resolution yang lebih matang.