test add tokenizer

This commit is contained in:
polv committed 2026-08-28 00:54:20 +07:00
1 parent 7e8c38a80f
commit 9dfae88805
11 files changed
+233 -20

No files matched your search

@@ -1,5 +1,6 @@
<template>
<span>
<span ref="main"><slot /></span>
<button type="button" @click="playAudio()">▶️</button>
<audio ref="audio" style="display: none"></audio>
</span>
@@ -17,6 +18,7 @@ defineExpose({
playAudio,
});
const mainEl = useTemplateRef("main");
const audioEl = useTemplateRef("audio");
async function playAudio() {
@@ -31,6 +33,10 @@ async function playAudio() {
let src = props.src;
if (src.startsWith(uploadURL)) {
src = src.replace(uploadURL, "/uploads");
if (mainEl.value) {
src += `?text=${encodeURIComponent(mainEl.value.innerText.trim())}`;
}
audioEl.value.onerror = () => {
if (!audioEl.value) return;
console.log(audioEl.value.error, src);
+1 -1
View File
@@ -40,7 +40,7 @@ async function gtts(
) {
text = text.replace(/["]/g, " ");
const saveroot = "docs/.vitepress/cache/gtts";
const saveroot = ".gtts";
fs.mkdirSync(saveroot, { recursive: true });
const fileListJSON = path.join(saveroot, "gtts.json");
+54 -7
View File
@@ -2,19 +2,55 @@ import fs from "node:fs";
import type { IncomingMessage, ServerResponse } from "node:http";
import path from "node:path";
import type { Connect, Plugin, ViteDevServer } from "vite";
import sqlite3 from "better-sqlite3";
import { Tokenizer } from "./tokenizer";
export function mp3DownloaderPlugin(): Plugin {
return {
name: "mp3-downloader",
configureServer(server: ViteDevServer) {
async configureServer(server: ViteDevServer) {
const db = sqlite3(".sentence-mp3.db");
db.exec(/* sql */ `
CREATE TABLE IF NOT EXISTS sentence (
id INTEGER NOT NULL PRIMARY KEY,
ko TEXT NOT NULL,
original TEXT NOT NULL,
[url] TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_sentence_u_url ON sentence([url]);
CREATE VIRTUAL TABLE IF NOT EXISTS sentence_fts USING fts5 (
ko,
original UNINDEXED,
[url] UNINDEXED,
content='sentence',
content_rowid='id'
);
-- Triggers to keep the FTS index up to date.
CREATE TRIGGER IF NOT EXISTS t1_ai AFTER INSERT ON sentence BEGIN
INSERT INTO sentence_fts(rowid, ko) VALUES (new.id, new.ko);
END;
CREATE TRIGGER IF NOT EXISTS t1_ad AFTER DELETE ON sentence BEGIN
INSERT INTO sentence_fts(fts_idx, rowid, ko) VALUES('delete', old.id, old.ko);
END;
CREATE TRIGGER IF NOT EXISTS t1_au AFTER UPDATE ON sentence BEGIN
INSERT INTO sentence_fts(fts_idx, rowid, ko) VALUES('delete', old.id, old.ko);
INSERT INTO sentence_fts(rowid, ko) VALUES (new.id, new.ko);
END;
`);
// Vite dev server middlewares are connect-style: (req, res, next)
server.middlewares.use(async (req, res, next) => {
const reqUrl = req.url;
if (!reqUrl || !reqUrl.startsWith("/uploads/")) return next();
if (!reqUrl.endsWith(".mp3")) return next();
const url = new URL(reqUrl, "http://localhost");
if (!url.pathname.endsWith(".mp3")) return next();
try {
await mp3Downloader(req, res, next);
await mp3Downloader(db, await Tokenizer.init(), url, res, next);
} catch (e) {
console.error(e, reqUrl);
next();
@@ -25,13 +61,24 @@ export function mp3DownloaderPlugin(): Plugin {
}
async function mp3Downloader(
req: Connect.IncomingMessage,
db: ReturnType<typeof sqlite3>,
tokenizer: Tokenizer,
url: URL,
res: ServerResponse<IncomingMessage>,
next: Connect.NextFunction,
) {
const url = new URL(`https://www.howtostudykorean.com/wp-content${req.url}`);
const downloadURL = new URL(
`https://www.howtostudykorean.com/wp-content${url.pathname}`,
);
const segs = req.url!.slice(1).split("/");
const text = url.searchParams.get("text");
if (text) {
db.prepare(
"INSERT OR IGNORE INTO sentence ([url], ko, original) VALUES (?, ?, ?)",
).run(downloadURL.href, tokenizer.normalize(text), text);
}
const segs = url.pathname.slice(1).split("/");
const filename = segs.pop()!;
const saveroot = "docs/public";
const saveto = path.join(saveroot, ...segs, filename);
@@ -42,7 +89,7 @@ async function mp3Downloader(
fs.mkdirSync(path.join(saveroot, ...segs), { recursive: true });
const r = await fetch(url, { redirect: "follow" });
const r = await fetch(downloadURL, { redirect: "follow" });
if (!r.ok) {
console.error(r);
return next();
+22
View File
@@ -0,0 +1,22 @@
import { MeCab } from "kuromoji-ko";
export class Tokenizer {
static async init() {
return new Tokenizer(
await MeCab.create({
engine: "ko",
dictPath: "./node_modules/kuromoji-ko/dict",
}),
);
}
private constructor(public mecab: MeCab) {}
normalize(s: string) {
return this.mecab
.parse(s)
.filter((t) => t.pos.some((p) => /^[NVM]/.test(p)))
.map((t) => t.lemma)
.join(" ");
}
}