|
1
|
#!/usr/bin/env bash |
|
2
|
# ============================================================================= |
|
3
|
# onde/crates/onde-wasm/build.sh |
|
4
|
# |
|
5
|
# One-command wasm-pack build for the onde WASM Whisper engine. |
|
6
|
# |
|
7
|
# Prerequisites |
|
8
|
# ------------- |
|
9
|
# • Rust with the wasm32-unknown-unknown target: |
|
10
|
# rustup target add wasm32-unknown-unknown |
|
11
|
# |
|
12
|
# • wasm-pack: |
|
13
|
# cargo install wasm-pack |
|
14
|
# |
|
15
|
# • (Optional) wasm-opt for smaller binaries — installed automatically by |
|
16
|
# wasm-pack if missing, but a pre-installed version is faster. |
|
17
|
# |
|
18
|
# Usage |
|
19
|
# ----- |
|
20
|
# # Standard release build |
|
21
|
# ./build.sh |
|
22
|
# |
|
23
|
# # Debug build (faster compile, larger .wasm, source-mapped) |
|
24
|
# BUILD_TYPE=debug ./build.sh |
|
25
|
# |
|
26
|
# # Override the output directory (default: onde/crates/onde-wasm/pkg) |
|
27
|
# OUT_DIR=/path/to/output ./build.sh |
|
28
|
# |
|
29
|
# Output |
|
30
|
# ------ |
|
31
|
# pkg/ |
|
32
|
# onde_wasm.js — ES module glue |
|
33
|
# onde_wasm_bg.wasm — compiled WASM binary |
|
34
|
# onde_wasm.d.ts — TypeScript declarations for WhisperDecoder |
|
35
|
# onde_wasm_bg.wasm.d.ts |
|
36
|
# package.json |
|
37
|
# |
|
38
|
# Model assets (download separately — see section below) |
|
39
|
# ------------------------------------------------------ |
|
40
|
# The WhisperDecoder constructor requires four byte arrays that are NOT |
|
41
|
# bundled into the .wasm binary. The JS consumer must fetch them from |
|
42
|
# HuggingFace and pass them as Uint8Arrays. |
|
43
|
# |
|
44
|
# Recommended model: whisper-tiny.en (non-quantized, ~75 MB total) |
|
45
|
# |
|
46
|
# WEIGHTS https://huggingface.co/openai/whisper-tiny.en/resolve/main/model.safetensors |
|
47
|
# TOKENIZER https://huggingface.co/openai/whisper-tiny.en/resolve/main/tokenizer.json |
|
48
|
# CONFIG https://huggingface.co/openai/whisper-tiny.en/resolve/main/config.json |
|
49
|
# MEL https://huggingface.co/spaces/lmz/candle-whisper/resolve/main/mel_filters.safetensors |
|
50
|
# |
|
51
|
# Recommended model: whisper-tiny (multilingual, ~75 MB total) |
|
52
|
# |
|
53
|
# WEIGHTS https://huggingface.co/openai/whisper-tiny/resolve/main/model.safetensors |
|
54
|
# TOKENIZER https://huggingface.co/openai/whisper-tiny/resolve/main/tokenizer.json |
|
55
|
# CONFIG https://huggingface.co/openai/whisper-tiny/resolve/main/config.json |
|
56
|
# MEL https://huggingface.co/spaces/lmz/candle-whisper/resolve/main/mel_filters.safetensors |
|
57
|
# |
|
58
|
# Quantized model: whisper-tiny.en Q8_0 (~42 MB, faster load) |
|
59
|
# |
|
60
|
# WEIGHTS https://huggingface.co/lmz/candle-whisper/resolve/main/model-tiny-en-q80.gguf |
|
61
|
# TOKENIZER https://huggingface.co/lmz/candle-whisper/resolve/main/tokenizer-tiny-en.json |
|
62
|
# CONFIG https://huggingface.co/lmz/candle-whisper/resolve/main/config-tiny-en.json |
|
63
|
# MEL https://huggingface.co/spaces/lmz/candle-whisper/resolve/main/mel_filters.safetensors |
|
64
|
# |
|
65
|
# Run the download helper: |
|
66
|
# ./build.sh --download-assets |
|
67
|
# |
|
68
|
# Vite integration (karokowe-connected-devices) |
|
69
|
# --------------------------------------------- |
|
70
|
# 1. Copy pkg/ into your project, e.g.: |
|
71
|
# cp -r pkg/ frontend/karokowe-connected-devices/src/wasm/onde/ |
|
72
|
# |
|
73
|
# 2. Add to vite.config.ts: |
|
74
|
# import { defineConfig } from "vite"; |
|
75
|
# export default defineConfig({ |
|
76
|
# optimizeDeps: { exclude: ["onde-wasm"] }, |
|
77
|
# server: { |
|
78
|
# headers: { |
|
79
|
# "Cross-Origin-Opener-Policy": "same-origin", |
|
80
|
# "Cross-Origin-Embedder-Policy": "require-corp", |
|
81
|
# }, |
|
82
|
# }, |
|
83
|
# }); |
|
84
|
# |
|
85
|
# NOTE: The COOP/COEP headers are NOT required by this pure-Rust WASM |
|
86
|
# build (no SharedArrayBuffer / pthreads are used — inference runs |
|
87
|
# synchronously on the JS thread). They are shown here for completeness |
|
88
|
# if you later add a Web Worker wrapper. |
|
89
|
# |
|
90
|
# 3. In your Worker or module: |
|
91
|
# import init, { WhisperDecoder } from "./onde_wasm.js"; |
|
92
|
# await init(); |
|
93
|
# const decoder = new WhisperDecoder( |
|
94
|
# weightsBytes, tokenizerBytes, melFiltersBytes, configBytes, |
|
95
|
# false, // quantized |
|
96
|
# false, // is_multilingual |
|
97
|
# true, // timestamps |
|
98
|
# null, // task |
|
99
|
# null, // language |
|
100
|
# ); |
|
101
|
# const json = decoder.decode(wavBytes); |
|
102
|
# const result = JSON.parse(json); |
|
103
|
# // result.text — full transcript |
|
104
|
# // result.segments[] — [{ start, end, text }] |
|
105
|
# |
|
106
|
# ============================================================================= |
|
107
|
|
|
108
|
set -euo pipefail |
|
109
|
|
|
110
|
# ── Script location ─────────────────────────────────────────────────────────── |
|
111
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
|
112
|
|
|
113
|
# ── Parameters ───────────────────────────────────────────────────────────────── |
|
114
|
BUILD_TYPE="${BUILD_TYPE:-release}" # "release" or "debug" |
|
115
|
OUT_DIR="${OUT_DIR:-${SCRIPT_DIR}/pkg}" |
|
116
|
|
|
117
|
# ── Colours ─────────────────────────────────────────────────────────────────── |
|
118
|
if [ -t 1 ]; then |
|
119
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' |
|
120
|
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' |
|
121
|
else |
|
122
|
RED=''; GREEN=''; YELLOW=''; CYAN=''; BOLD=''; RESET='' |
|
123
|
fi |
|
124
|
|
|
125
|
log_info() { echo -e "${CYAN}[onde-wasm]${RESET} $*"; } |
|
126
|
log_ok() { echo -e "${GREEN}[onde-wasm]${RESET} $*"; } |
|
127
|
log_warn() { echo -e "${YELLOW}[onde-wasm]${RESET} $*"; } |
|
128
|
log_error() { echo -e "${RED}[onde-wasm] ERROR:${RESET} $*" >&2; } |
|
129
|
log_section() { echo -e "\n${BOLD}── $* ─────────────────────────────────${RESET}"; } |
|
130
|
|
|
131
|
# ── --download-assets helper ─────────────────────────────────────────────────── |
|
132
|
download_assets() { |
|
133
|
log_section "Downloading model assets" |
|
134
|
|
|
135
|
ASSETS_DIR="${SCRIPT_DIR}/assets" |
|
136
|
mkdir -p "${ASSETS_DIR}" |
|
137
|
|
|
138
|
declare -A URLS=( |
|
139
|
["model.safetensors"]="https://huggingface.co/openai/whisper-tiny.en/resolve/main/model.safetensors" |
|
140
|
["tokenizer.json"]="https://huggingface.co/openai/whisper-tiny.en/resolve/main/tokenizer.json" |
|
141
|
["config.json"]="https://huggingface.co/openai/whisper-tiny.en/resolve/main/config.json" |
|
142
|
["mel_filters.safetensors"]="https://huggingface.co/spaces/lmz/candle-whisper/resolve/main/mel_filters.safetensors" |
|
143
|
) |
|
144
|
|
|
145
|
for fname in "${!URLS[@]}"; do |
|
146
|
dest="${ASSETS_DIR}/${fname}" |
|
147
|
if [ -f "${dest}" ]; then |
|
148
|
log_info " ${fname} — already present, skipping" |
|
149
|
continue |
|
150
|
fi |
|
151
|
log_info " Downloading ${fname}…" |
|
152
|
curl -fL --progress-bar -o "${dest}" "${URLS[$fname]}" |
|
153
|
log_ok " ${fname} saved ($(du -sh "${dest}" | cut -f1))" |
|
154
|
done |
|
155
|
|
|
156
|
log_ok "All assets in ${ASSETS_DIR}/" |
|
157
|
echo |
|
158
|
echo " Pass these to WhisperDecoder in JS:" |
|
159
|
echo " weights: ${ASSETS_DIR}/model.safetensors" |
|
160
|
echo " tokenizer: ${ASSETS_DIR}/tokenizer.json" |
|
161
|
echo " config: ${ASSETS_DIR}/config.json" |
|
162
|
echo " mel_filters: ${ASSETS_DIR}/mel_filters.safetensors" |
|
163
|
exit 0 |
|
164
|
} |
|
165
|
|
|
166
|
if [[ "${1:-}" == "--download-assets" ]]; then |
|
167
|
download_assets |
|
168
|
fi |
|
169
|
|
|
170
|
# ── Preflight ────────────────────────────────────────────────────────────────── |
|
171
|
log_section "Preflight" |
|
172
|
|
|
173
|
if ! command -v wasm-pack &>/dev/null; then |
|
174
|
log_error "wasm-pack not found." |
|
175
|
echo |
|
176
|
echo " Install with:" |
|
177
|
echo " cargo install wasm-pack" |
|
178
|
echo |
|
179
|
exit 1 |
|
180
|
fi |
|
181
|
|
|
182
|
WASM_PACK_VERSION=$(wasm-pack --version) |
|
183
|
log_info "wasm-pack: ${WASM_PACK_VERSION}" |
|
184
|
|
|
185
|
if ! rustup target list --installed | grep -q "wasm32-unknown-unknown"; then |
|
186
|
log_warn "wasm32-unknown-unknown target not installed — installing now…" |
|
187
|
rustup target add wasm32-unknown-unknown |
|
188
|
fi |
|
189
|
|
|
190
|
log_info "Build type : ${BUILD_TYPE}" |
|
191
|
log_info "Output dir : ${OUT_DIR}" |
|
192
|
|
|
193
|
# ── Build ────────────────────────────────────────────────────────────────────── |
|
194
|
log_section "Build" |
|
195
|
|
|
196
|
WASM_PACK_PROFILE_FLAG="--release" |
|
197
|
if [[ "${BUILD_TYPE}" == "debug" ]]; then |
|
198
|
WASM_PACK_PROFILE_FLAG="--dev" |
|
199
|
fi |
|
200
|
|
|
201
|
wasm-pack build \ |
|
202
|
--target web \ |
|
203
|
--out-dir "${OUT_DIR}" \ |
|
204
|
${WASM_PACK_PROFILE_FLAG} \ |
|
205
|
"${SCRIPT_DIR}" |
|
206
|
|
|
207
|
log_ok "wasm-pack build complete." |
|
208
|
|
|
209
|
# ── Post-process: fix package.json name ─────────────────────────────────────── |
|
210
|
# wasm-pack emits the crate name as the package name; rename to onde-wasm |
|
211
|
# so the Vite import path is predictable. |
|
212
|
PKG_JSON="${OUT_DIR}/package.json" |
|
213
|
if [ -f "${PKG_JSON}" ]; then |
|
214
|
# Use sed for portable in-place replacement (BSD and GNU sed both support this). |
|
215
|
sed -i.bak 's/"name": "onde_wasm"/"name": "onde-wasm"/' "${PKG_JSON}" |
|
216
|
rm -f "${PKG_JSON}.bak" |
|
217
|
log_info "package.json name → onde-wasm" |
|
218
|
fi |
|
219
|
|
|
220
|
# ── Summary ─────────────────────────────────────────────────────────────────── |
|
221
|
log_section "Output" |
|
222
|
|
|
223
|
echo |
|
224
|
log_ok "onde-wasm build succeeded!" |
|
225
|
echo |
|
226
|
echo " Artefacts:" |
|
227
|
ls -lh "${OUT_DIR}/"*.{js,wasm,ts} 2>/dev/null \ |
|
228
|
| grep -v '^total' \ |
|
229
|
| awk '{printf " %-40s %s\n", $9, $5}' \ |
|
230
|
|| true |
|
231
|
echo |
|
232
|
echo " Next steps:" |
|
233
|
echo |
|
234
|
echo " 1. Download model assets (first time only):" |
|
235
|
echo " ${BASH_SOURCE[0]} --download-assets" |
|
236
|
echo |
|
237
|
echo " 2. Copy pkg/ into your Vite project:" |
|
238
|
echo " cp -r ${OUT_DIR}/. frontend/karokowe-connected-devices/src/wasm/onde/" |
|
239
|
echo |
|
240
|
echo " 3. Import in a Web Worker:" |
|
241
|
echo " import init, { WhisperDecoder } from \"./onde_wasm.js\";" |
|
242
|
echo " await init();" |
|
243
|
echo " const decoder = new WhisperDecoder(" |
|
244
|
echo " weightsBytes, tokenizerBytes, melFiltersBytes, configBytes," |
|
245
|
echo " false, false, true, null, null" |
|
246
|
echo " );" |
|
247
|
echo " const result = JSON.parse(decoder.decode(wavBytes));" |
|
248
|
echo |