Skip to content

Commit 1516f0a

Browse files
luongndclaude
andcommitted
fix(db): create upload_chunks before ALTERs + bump Soniox v5, v1.3.2
Fresh installs crashed on upload with "no such column: status". In _migrate_v2 the upload_chunks ALTERs ran before the CREATE TABLE, so on a fresh DB the ALTER hit "no such table" (silently dropped — the loop only ignores "duplicate column") and the table was created without status/error_message/segments_json. Create the child tables first and declare the three columns inline; keep the ALTERs to top up old DBs. Also bump Soniox to v5 (stt-async-v5 for upload, stt-rt-v5 for realtime) — same price, better accuracy + speaker separation, name-only change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d17e116 commit 1516f0a

8 files changed

Lines changed: 53 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
Tất cả thay đổi đáng chú ý của Scribble được ghi tại đây.
44

5+
## [1.3.2] - 2026-06-22
6+
7+
### Fixed
8+
- Sửa lỗi `no such column: status` khi upload file trên máy cài mới. Migration tạo bảng `upload_chunks` sai thứ tự (ALTER chạy trước CREATE) khiến bảng thiếu cột `status/error_message/segments_json` — nay tạo bảng trước, khai báo cột inline, ALTER chỉ top-up cho DB cũ.
9+
10+
### Changed
11+
- Nâng Soniox lên model v5 (`stt-async-v5` cho upload, `stt-rt-v5` cho ghi âm realtime) — chất lượng nhận diện và tách người nói tốt hơn, giá không đổi.
12+
513
## [1.3.1] - 2026-06-09
614

715
### Fixed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "voicescribe",
33
"private": true,
4-
"version": "1.3.1",
4+
"version": "1.3.2",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-python/db.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -109,24 +109,15 @@ def _migrate_v2(self):
109109
Existing rows get default values; realtime flow unaffected.
110110
"""
111111
conn = self._conn()
112-
migrations = [
113-
"ALTER TABLE meetings ADD COLUMN source_type TEXT DEFAULT 'realtime'",
114-
"ALTER TABLE meetings ADD COLUMN file_hash TEXT DEFAULT NULL",
115-
"ALTER TABLE meetings ADD COLUMN source_filename TEXT DEFAULT NULL",
116-
"CREATE INDEX IF NOT EXISTS idx_meetings_file_hash ON meetings(file_hash)",
117-
# v1.2.13: per-chunk failure tracking + denormalized failed count
118-
# for fast list queries (avoids COUNT(*) per meeting).
119-
"ALTER TABLE meetings ADD COLUMN failed_chunks_count INTEGER DEFAULT 0",
120-
"ALTER TABLE upload_chunks ADD COLUMN status TEXT DEFAULT NULL",
121-
"ALTER TABLE upload_chunks ADD COLUMN error_message TEXT DEFAULT NULL",
122-
"ALTER TABLE upload_chunks ADD COLUMN segments_json TEXT DEFAULT NULL",
123-
]
124-
for sql in migrations:
125-
try:
126-
conn.execute(sql)
127-
except sqlite3.OperationalError as e:
128-
if "duplicate column" not in str(e).lower():
129-
log.warning("Migration skipped (%s): %s", sql[:60], e)
112+
113+
# Create child tables FIRST — before the ALTER migrations below.
114+
# ORDER MATTERS: the upload_chunks ALTERs target this table. On a fresh
115+
# DB, an ALTER that ran before CREATE threw "no such table", which the
116+
# loop swallowed (it only ignores "duplicate column"). That left
117+
# upload_chunks without status/error_message/segments_json and broke
118+
# every fresh install with "no such column: status" on upload. The
119+
# three columns are also declared inline here so a fresh DB is complete
120+
# even when the ALTERs are no-ops.
130121

131122
# ── Per-chunk progress (upload pipeline resume) ──
132123
# text IS NULL until STT completes; embedding BLOB is the raw 512×f32
@@ -141,6 +132,9 @@ def _migrate_v2(self):
141132
text TEXT DEFAULT NULL,
142133
embedding BLOB DEFAULT NULL,
143134
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
135+
status TEXT DEFAULT NULL,
136+
error_message TEXT DEFAULT NULL,
137+
segments_json TEXT DEFAULT NULL,
144138
PRIMARY KEY (meeting_id, chunk_idx)
145139
)
146140
"""
@@ -170,6 +164,29 @@ def _migrate_v2(self):
170164
conn.execute(
171165
"CREATE INDEX IF NOT EXISTS idx_attachments_meeting ON meeting_attachments(meeting_id)"
172166
)
167+
168+
# Column top-ups for DBs created by older versions. The tables above
169+
# already exist by now, so an upload_chunks ALTER either adds the
170+
# missing column (old DB) or is ignored as "duplicate column".
171+
migrations = [
172+
"ALTER TABLE meetings ADD COLUMN source_type TEXT DEFAULT 'realtime'",
173+
"ALTER TABLE meetings ADD COLUMN file_hash TEXT DEFAULT NULL",
174+
"ALTER TABLE meetings ADD COLUMN source_filename TEXT DEFAULT NULL",
175+
"CREATE INDEX IF NOT EXISTS idx_meetings_file_hash ON meetings(file_hash)",
176+
# v1.2.13: per-chunk failure tracking + denormalized failed count
177+
# for fast list queries (avoids COUNT(*) per meeting).
178+
"ALTER TABLE meetings ADD COLUMN failed_chunks_count INTEGER DEFAULT 0",
179+
"ALTER TABLE upload_chunks ADD COLUMN status TEXT DEFAULT NULL",
180+
"ALTER TABLE upload_chunks ADD COLUMN error_message TEXT DEFAULT NULL",
181+
"ALTER TABLE upload_chunks ADD COLUMN segments_json TEXT DEFAULT NULL",
182+
]
183+
for sql in migrations:
184+
try:
185+
conn.execute(sql)
186+
except sqlite3.OperationalError as e:
187+
if "duplicate column" not in str(e).lower():
188+
log.warning("Migration skipped (%s): %s", sql[:60], e)
189+
173190
conn.commit()
174191

175192
# ─── Upload chunks (resume support) ───

src-python/services/upload_pipeline.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def _normalize_stt_provider(raw: str | None) -> str:
7676
# setting `upload_max_duration_hours` if a user has legit >24h needs.
7777
DEFAULT_MAX_DURATION_HOURS = 24
7878

79-
# Soniox stt-async-v4 has an ~5h cap per submission. We split at 3.5h to
79+
# Soniox stt-async-v5 has an ~5h cap per submission. We split at 3.5h to
8080
# leave a 30% safety buffer for network slowness, peak-load queueing, and
8181
# Soniox-side variance. Files ≤ 3.5h take the single-shot path (preserves
8282
# globally-consistent speaker diarization across the whole file).
@@ -647,7 +647,7 @@ async def _run_soniox_pipeline(
647647
duration_sec: float, tmp_root: Path,
648648
) -> list[dict]:
649649
"""Soniox STT pipeline. Auto-splits files > 3.5h into chunks because
650-
Soniox stt-async-v4 caps at ~5h per submission.
650+
Soniox stt-async-v5 caps at ~5h per submission.
651651
652652
Returns transcript_parts ready for ``db.update_meeting(transcript=...)``.
653653
- File ≤ 3.5h: single-shot — Soniox does globally-consistent diarization
@@ -1561,7 +1561,7 @@ async def _process_one(chunk: AudioChunk):
15611561

15621562
# Dispatch to the configured provider. Nvidia uses streaming gRPC
15631563
# (offline_recognize unavailable for vi/zh). Soniox uses the
1564-
# async file API (stt-async-v4) with auto-cleanup.
1564+
# async file API (stt-async-v5) with auto-cleanup.
15651565
if stt_provider == "nvidia":
15661566
stt_task = asyncio.to_thread(
15671567
transcribe_nvidia_streaming, str(chunk.path), nvidia_key, riva_lang

src-python/stt.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ def transcribe_soniox_file_id(
510510
)
511511
client = SonioxClient(api_key=api_key)
512512
config = CreateTranscriptionConfig(
513-
model="stt-async-v4",
513+
model="stt-async-v5",
514514
language_hints=hints,
515515
enable_speaker_diarization=True,
516516
enable_language_identification=False,
@@ -916,7 +916,7 @@ class SonioxStreamingSTT:
916916
- receive_events() runs on the calling thread (results generator)
917917
"""
918918

919-
# Soniox stt-rt-v4 has a server-side session duration cap of 300 min (5h,
919+
# Soniox stt-rt-v5 has a server-side session duration cap of 300 min (5h,
920920
# fixed — see docs/stt/rt/limits-and-quotas). When the cap fires OR the
921921
# connection idle-closes, the server closes the WS and our event iterator
922922
# exits silently. We auto-reconnect up to this many times.
@@ -962,7 +962,7 @@ def _open_session(self):
962962
from soniox.types import RealtimeSTTConfig, TranslationConfig
963963

964964
config = RealtimeSTTConfig(
965-
model="stt-rt-v4",
965+
model="stt-rt-v5",
966966
audio_format="pcm_s16le",
967967
sample_rate=16000,
968968
num_channels=1,

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "voicescribe"
3-
version = "1.3.1"
3+
version = "1.3.2"
44
description = "Scribble - Meeting Minutes"
55
authors = ["you"]
66
edition = "2021"

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "Scribble",
4-
"version": "1.3.1",
4+
"version": "1.3.2",
55
"identifier": "com.scribble.app",
66
"build": {
77
"beforeDevCommand": "pnpm dev",

0 commit comments

Comments
 (0)