Skip to content

Commit 68eb3a0

Browse files
许君山许君山
authored andcommitted
feat: integrate Jisho.org API to support virtually all Japanese verbs for autocomplete
1 parent 4f19157 commit 68eb3a0

1 file changed

Lines changed: 78 additions & 4 deletions

File tree

backend/server.js

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,61 @@ import fs from 'fs';
77
import path from 'path';
88
import { fileURLToPath } from 'url';
99
import { Ollama } from 'ollama';
10+
import https from 'https';
1011

1112
const __filename = fileURLToPath(import.meta.url);
1213
const __dirname = path.dirname(__filename);
1314

1415
// 读取动词库
1516
const commonVerbs = JSON.parse(fs.readFileSync(path.join(__dirname, 'common-verbs.json'), 'utf8'));
1617

18+
// 调用 Jisho API 获取更多动词补充
19+
function searchJisho(keyword) {
20+
return new Promise((resolve, reject) => {
21+
https.get(`https://jisho.org/api/v1/search/words?keyword=${encodeURIComponent(keyword)}`, (res) => {
22+
let data = '';
23+
res.on('data', chunk => data += chunk);
24+
res.on('end', () => {
25+
try {
26+
const parsed = JSON.parse(data);
27+
const verbs = [];
28+
if (!parsed.data) return resolve([]);
29+
30+
for (const item of parsed.data) {
31+
const senses = item.senses || [];
32+
let isVerb = false;
33+
let meaning = '';
34+
35+
for (const sense of senses) {
36+
const pos = sense.parts_of_speech || [];
37+
if (pos.some(p => p.toLowerCase().includes('verb'))) {
38+
isVerb = true;
39+
meaning = sense.english_definitions.slice(0, 2).join(', ');
40+
break;
41+
}
42+
}
43+
44+
if (isVerb && item.japanese && item.japanese.length > 0) {
45+
const kanji = item.japanese[0].word || item.japanese[0].reading;
46+
const kana = item.japanese[0].reading || kanji;
47+
verbs.push({
48+
kanji,
49+
kana,
50+
romaji: wanakana.toRomaji(kana),
51+
meaning
52+
});
53+
}
54+
}
55+
resolve(verbs);
56+
} catch (e) {
57+
reject(e);
58+
}
59+
});
60+
}).on('error', reject);
61+
});
62+
}
63+
64+
// 初始化 Ollama
1765
const app = express();
1866
const PORT = process.env.PORT || 3000;
1967

@@ -159,23 +207,49 @@ ${JSON.stringify(conjugationResult, null, 2)}
159207
});
160208

161209
// 动词自动补全 API
162-
app.get('/api/suggest', (req, res) => {
210+
app.get('/api/suggest', async (req, res) => {
163211
try {
164212
const { q } = req.query;
165213
if (!q || q.trim() === '') {
166214
return res.json([]);
167215
}
168216

169217
const query = q.toLowerCase().trim();
170-
const suggestions = commonVerbs.filter(verb => {
218+
219+
// 1. 本地高频词库快速匹配
220+
const localSuggestions = commonVerbs.filter(verb => {
171221
return verb.kanji.includes(query) ||
172222
verb.kana.includes(query) ||
173223
verb.romaji.includes(query) ||
174224
verb.meaning.includes(query);
175-
}).slice(0, 8); // 最多返回8条记录,避免前端渲染过大
225+
});
226+
227+
// 2. 并行调用 Jisho API 获取更广泛的词汇(限制等待时间)
228+
let jishoSuggestions = [];
229+
try {
230+
jishoSuggestions = await Promise.race([
231+
searchJisho(query),
232+
new Promise(resolve => setTimeout(() => resolve([]), 800)) // 800ms 超时,保证输入流畅
233+
]);
234+
} catch(e) {
235+
console.error('Jisho API fetch failed', e);
236+
}
237+
238+
// 3. 合并去重(以 kanji 作为唯一标识)
239+
const merged = [...localSuggestions, ...jishoSuggestions];
240+
const unique = [];
241+
const seen = new Set();
242+
243+
for (const verb of merged) {
244+
if (!seen.has(verb.kanji)) {
245+
seen.add(verb.kanji);
246+
unique.push(verb);
247+
}
248+
}
176249

177-
res.json(suggestions);
250+
res.json(unique.slice(0, 8)); // 最多返回8条记录
178251
} catch (error) {
252+
console.error(error);
179253
res.status(500).json({ error: 'Failed to fetch suggestions' });
180254
}
181255
});

0 commit comments

Comments
 (0)