feat(dict): high-speed offline dictionary with wordforms and zero daemon SQLite mmap
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import sqlite3
|
||||
import contextlib
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
from app import config
|
||||
|
||||
@@ -174,6 +175,59 @@ def _migrate(conn: sqlite3.Connection):
|
||||
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);")
|
||||
|
||||
# Ensure offline_dictionary table exists
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS offline_dictionary (
|
||||
term TEXT PRIMARY KEY COLLATE NOCASE,
|
||||
translation TEXT NOT NULL,
|
||||
phonetic TEXT DEFAULT '',
|
||||
pos TEXT DEFAULT ''
|
||||
);
|
||||
""")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_offline_dict_term ON offline_dictionary(term COLLATE NOCASE);")
|
||||
|
||||
# Auto-seed offline dictionary if empty
|
||||
_seed_offline_dictionary(conn)
|
||||
|
||||
|
||||
def _seed_offline_dictionary(conn: sqlite3.Connection):
|
||||
"""Seeds the offline dictionary from data/offline_dict.tsv.gz if the table is empty."""
|
||||
try:
|
||||
count = conn.execute("SELECT COUNT(*) FROM offline_dictionary").fetchone()[0]
|
||||
if count > 0:
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
archive_candidates = [
|
||||
config.BASE_DIR / "data" / "offline_dict.tsv.gz",
|
||||
config.BASE_DIR / "app" / "data" / "offline_dict.tsv.gz",
|
||||
Path("/app/data/offline_dict.tsv.gz"),
|
||||
]
|
||||
archive_path = next((p for p in archive_candidates if p.is_file()), None)
|
||||
if not archive_path:
|
||||
return
|
||||
|
||||
import gzip
|
||||
|
||||
batch = []
|
||||
with gzip.open(archive_path, "rt", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
parts = line.rstrip("\r\n").split("\t")
|
||||
if len(parts) == 4:
|
||||
batch.append(parts)
|
||||
if len(batch) >= 10000:
|
||||
conn.executemany(
|
||||
"INSERT OR IGNORE INTO offline_dictionary (term, translation, phonetic, pos) VALUES (?, ?, ?, ?)",
|
||||
batch,
|
||||
)
|
||||
batch.clear()
|
||||
if batch:
|
||||
conn.executemany(
|
||||
"INSERT OR IGNORE INTO offline_dictionary (term, translation, phonetic, pos) VALUES (?, ?, ?, ?)",
|
||||
batch,
|
||||
)
|
||||
|
||||
|
||||
def init_db():
|
||||
config.DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -271,6 +325,14 @@ def init_db():
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_books_user ON books(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_books_user_updated ON books(user_id, updated_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS offline_dictionary (
|
||||
term TEXT PRIMARY KEY COLLATE NOCASE,
|
||||
translation TEXT NOT NULL,
|
||||
phonetic TEXT DEFAULT '',
|
||||
pos TEXT DEFAULT ''
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_offline_dict_term ON offline_dictionary(term COLLATE NOCASE);
|
||||
""")
|
||||
# Apply migrations for pre-existing databases
|
||||
_migrate(conn)
|
||||
|
||||
+34
-2
@@ -240,8 +240,40 @@ async def lookup_term(query: str) -> Dict[str, Any]:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
words = query_clean.split()
|
||||
is_sentence = len(words) > 4
|
||||
# Check offline dictionary first (Zero Daemon, < 0.1ms via SQLite mmap)
|
||||
words = query_clean.split()
|
||||
is_sentence = len(words) > 4
|
||||
if not is_sentence:
|
||||
try:
|
||||
off_row = conn.execute(
|
||||
"SELECT term, translation, phonetic, pos FROM offline_dictionary WHERE term = ? COLLATE NOCASE",
|
||||
(query_clean,)
|
||||
).fetchone()
|
||||
if off_row:
|
||||
raw_trans = off_row["translation"] or ""
|
||||
tr_list = [t.strip() for t in raw_trans.split(",") if t.strip()]
|
||||
res = {
|
||||
"query": query_clean,
|
||||
"is_sentence": False,
|
||||
"translation": raw_trans,
|
||||
"translations_list": tr_list,
|
||||
"translation_ru": raw_trans,
|
||||
"phonetic": off_row["phonetic"] or "",
|
||||
"audio_file": "",
|
||||
"audio_url": "",
|
||||
"part_of_speech": off_row["pos"] or "",
|
||||
"definitions": [],
|
||||
"examples": [],
|
||||
"source": "offline",
|
||||
}
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO dictionary_cache (query, response_json) VALUES (?, ?)",
|
||||
(query_clean, json.dumps(res, ensure_ascii=False))
|
||||
)
|
||||
return res
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
result: Dict[str, Any] = {
|
||||
"query": query_clean,
|
||||
|
||||
Reference in New Issue
Block a user