Skip to content

Commit 6d0a5af

Browse files
许君山许君山
authored andcommitted
feat: auto-detect verb type using kuromoji tokenizer
1 parent a2d1166 commit 6d0a5af

4 files changed

Lines changed: 113 additions & 25 deletions

File tree

backend/package-lock.json

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

backend/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"dev": "node --watch server.js"
1010
},
1111
"dependencies": {
12+
"cors": "^2.8.5",
1213
"express": "^4.18.2",
13-
"cors": "^2.8.5"
14-
},
15-
"devDependencies": {}
14+
"kuromoji": "^0.1.2"
15+
}
1616
}

backend/server.js

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import express from 'express';
22
import cors from 'cors';
3+
import kuromoji from 'kuromoji';
34
import { conjugate } from './conjugationEngine.js';
45

56
const app = express();
@@ -9,22 +10,69 @@ const PORT = process.env.PORT || 3000;
910
app.use(cors());
1011
app.use(express.json());
1112

13+
let tokenizer = null;
14+
15+
// 初始化 Kuromoji 分词器
16+
kuromoji.builder({ dicPath: 'node_modules/kuromoji/dict' }).build((err, _tokenizer) => {
17+
if (err) {
18+
console.error('Failed to build Kuromoji tokenizer:', err);
19+
} else {
20+
tokenizer = _tokenizer;
21+
console.log('Kuromoji tokenizer ready');
22+
}
23+
});
24+
25+
// 自动检测动词类型
26+
function detectVerbType(verb) {
27+
if (!tokenizer) throw new Error('Tokenizer not ready');
28+
29+
const tokens = tokenizer.tokenize(verb);
30+
if (tokens.length === 0) return null;
31+
32+
// 对于像 勉強する 这样的词,动词部分在最后
33+
let verbToken = tokens.slice().reverse().find(t => t.pos === '動詞');
34+
35+
// 如果没有找到动词,可能是输入的词有问题
36+
if (!verbToken) return null;
37+
38+
const cType = verbToken.conjugated_type;
39+
if (cType.includes('一段')) return 'ICHIDAN';
40+
if (cType.includes('五段')) return 'GODAN';
41+
if (cType.includes('サ変')) return 'SURU';
42+
if (cType.includes('カ変')) return 'KURU';
43+
44+
return null;
45+
}
46+
1247
// 健康检查
1348
app.get('/health', (req, res) => {
14-
res.json({ status: 'ok' });
49+
res.json({ status: 'ok', dictionaryReady: !!tokenizer });
1550
});
1651

1752
// 动词活用 API
1853
app.get('/api/conjugate', (req, res) => {
1954
try {
20-
const { verb, type } = req.query;
55+
let { verb, type } = req.query;
2156

22-
if (!verb || !type) {
57+
if (!verb) {
2358
return res.status(400).json({
24-
error: 'Missing required parameters: verb and type'
59+
error: 'Missing required parameter: verb'
2560
});
2661
}
2762

63+
// 如果前端没有传 type,就用 kuromoji 自动推断
64+
if (!type) {
65+
if (!tokenizer) {
66+
return res.status(503).json({ error: 'Dictionary is initializing, please try again later.' });
67+
}
68+
type = detectVerbType(verb);
69+
if (!type) {
70+
return res.status(400).json({
71+
error: `Could not automatically detect verb type for '${verb}'. Please ensure it is a valid dictionary form Japanese verb or provide the type manually.`
72+
});
73+
}
74+
}
75+
2876
const result = conjugate(verb, type);
2977
res.json(result);
3078
} catch (error) {

frontend/src/App.vue

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,6 @@
2222
>
2323
</div>
2424

25-
<div class="form-group">
26-
<label for="verb-type">动词类型</label>
27-
<select v-model="form.type" id="verb-type">
28-
<option value="">-- 选择类型 --</option>
29-
<option value="GODAN">五段动词 (Group 1)</option>
30-
<option value="ICHIDAN">一段动词 (Group 2)</option>
31-
<option value="SURU">サ变动词 (Group 3)</option>
32-
<option value="KURU">カ变动词 (Group 3)</option>
33-
</select>
34-
</div>
35-
3625
<button @click="conjugate" class="btn-primary">
3726
{{ loading ? '处理中...' : '活用' }}
3827
</button>
@@ -50,6 +39,10 @@
5039
<span class="label">原形</span>
5140
<span class="value">{{ result.dictionaryForm }}</span>
5241
</div>
42+
<div class="result-item">
43+
<span class="label">动词类型</span>
44+
<span class="value">{{ verbTypeMap[result.verbType] || result.verbType }}</span>
45+
</div>
5346
<div class="result-item">
5447
<span class="label">否定式</span>
5548
<span class="value">{{ result.negative }}</span>
@@ -173,29 +166,34 @@ import { ref } from 'vue';
173166
import axios from 'axios';
174167
175168
const form = ref({
176-
verb: '',
177-
type: ''
169+
verb: ''
178170
});
179171
180172
const result = ref(null);
181173
const loading = ref(false);
182174
const error = ref('');
183175
176+
const verbTypeMap = {
177+
GODAN: '五段动词',
178+
ICHIDAN: '一段动词',
179+
SURU: 'サ变动词',
180+
KURU: 'カ变动词'
181+
};
182+
184183
const conjugate = async () => {
185184
error.value = '';
186185
result.value = null;
187186
188-
if (!form.value.verb || !form.value.type) {
189-
error.value = '请输入动词和选择类型';
187+
if (!form.value.verb) {
188+
error.value = '请输入动词';
190189
return;
191190
}
192191
193192
loading.value = true;
194193
try {
195194
const response = await axios.get('/api/conjugate', {
196195
params: {
197-
verb: form.value.verb,
198-
type: form.value.type
196+
verb: form.value.verb
199197
}
200198
});
201199
result.value = response.data;

0 commit comments

Comments
 (0)