// Minimal client for the Greek WordNet API // Docs: https://greekwordnet.chs.harvard.edu/api const PROXY_BASE = '/api/wordnet'; const REMOTE_BASE = 'https://greekwordnet.chs.harvard.edu'; const BASE = (typeof window !== 'undefined') ? PROXY_BASE : REMOTE_BASE; const API_BASE = `${BASE}/api`; async function requestJson(url, options = {}) { // Add a short timeout to keep UI responsive const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs || 15000); try { const resp = await fetch(url, { ...options, signal: controller.signal }); if (!resp.ok) { const text = await resp.text().catch(() => ''); throw new Error(`HTTP ${resp.status} ${resp.statusText} for ${url} ${text ? '- ' + text.slice(0, 200) : ''}`); } // Most endpoints return JSON const ct = (resp.headers.get('content-type') || '').toLowerCase(); if (ct.includes('application/json')) { return await resp.json(); } // Fallback: try text return await resp.text(); } finally { clearTimeout(timeoutId); } } // ----- Lemmas ----- export async function searchLemmas(query) { if (!query) return []; const url = `${API_BASE}/lemmas?search=${encodeURIComponent(query)}`; return await requestJson(url); } export async function getLemma(headword, pos, morpho) { if (!headword) throw new Error('headword is required'); const parts = [encodeURIComponent(headword)]; if (pos) parts.push(encodeURIComponent(pos)); if (morpho) parts.push(encodeURIComponent(morpho)); const url = `${API_BASE}/lemmas/${parts.join('/')}/`; return await requestJson(url); } export async function getLemmaSynsets(headword, pos, morpho) { if (!headword) throw new Error('headword is required'); const parts = [encodeURIComponent(headword)]; if (pos) parts.push(encodeURIComponent(pos)); if (morpho) parts.push(encodeURIComponent(morpho)); const url = `${API_BASE}/lemmas/${parts.join('/')}/synsets`; return await requestJson(url); } export async function getLemmaRelations(headword, pos, morpho) { if (!headword) throw new Error('headword is required'); const parts = [encodeURIComponent(headword)]; if (pos) parts.push(encodeURIComponent(pos)); if (morpho) parts.push(encodeURIComponent(morpho)); const url = `${API_BASE}/lemmas/${parts.join('/')}/relations`; return await requestJson(url); } // ----- Synsets ----- export async function getSynset(pos, offset) { if (!pos || !offset) throw new Error('pos and offset are required'); const url = `${API_BASE}/synsets/${encodeURIComponent(pos)}/${encodeURIComponent(offset)}/`; return await requestJson(url); } export async function getSynsetLemmas(pos, offset) { if (!pos || !offset) throw new Error('pos and offset are required'); const url = `${API_BASE}/synsets/${encodeURIComponent(pos)}/${encodeURIComponent(offset)}/lemmas`; return await requestJson(url); } export async function getSynsetRelations(pos, offset) { if (!pos || !offset) throw new Error('pos and offset are required'); const url = `${API_BASE}/synsets/${encodeURIComponent(pos)}/${encodeURIComponent(offset)}/relations`; return await requestJson(url); } // options is forwarded to requestJson, so callers can impose a shorter timeout than the 15s default. // The word-hint pass uses 2s: it runs over the internet and must never hold up the interface. export async function searchSynsets(glossQuery, options) { if (!glossQuery) return []; const url = `${API_BASE}/synsets?search=${encodeURIComponent(glossQuery)}`; return await requestJson(url, options || {}); } // ----- Semfields ----- export async function getSemfield(ddcsCode) { if (!ddcsCode) throw new Error('ddcsCode is required'); const url = `${API_BASE}/semfields/${encodeURIComponent(ddcsCode)}`; return await requestJson(url); } export async function searchSemfields(query) { if (!query) return []; const url = `${API_BASE}/semfields?search=${encodeURIComponent(query)}`; return await requestJson(url); } // ----- Lemmatization / Translation / Sentiment ----- export async function lemmatize(form, pos) { if (!form) return []; const path1 = pos ? `/lemmatize/${encodeURIComponent(form)}/${encodeURIComponent(pos)}` : `/lemmatize/${encodeURIComponent(form)}`; try { return await requestJson(`${BASE}${path1}`); } catch (_) { // Fallback strategies if upstream lemmatizer errors: // 1) Try API search as a loose approximation try { const results = await requestJson(`${API_BASE}/lemmas?search=${encodeURIComponent(form)}`); return Array.isArray(results) ? results : []; } catch { // 2) Give up with an empty list to keep UI stable return []; } } } export async function translate(langIso639, word, pos) { if (!langIso639 || !word) throw new Error('lang and word are required'); const path = pos ? `/translate/${encodeURIComponent(langIso639)}/${encodeURIComponent(word)}/${encodeURIComponent(pos)}` : `/translate/${encodeURIComponent(langIso639)}/${encodeURIComponent(word)}`; return await requestJson(`${BASE}${path}`); } export async function analyzeSentiment(text, options = {}) { if (!text) return { polarity: 0, tokens: [] }; const body = { text, ...(options.weighting ? { weighting: options.weighting } : {}), ...(Array.isArray(options.excluded) ? { excluded: options.excluded } : {}) }; return await requestJson(`${BASE}/sentiment`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); } // Convenience: small helper to get synsets for a lemma headword quickly export async function lookupLemma(headword, pos, morpho) { const [lemmaInfo, synsets] = await Promise.allSettled([ getLemma(headword, pos, morpho), getLemmaSynsets(headword, pos, morpho) ]); return { lemma: lemmaInfo.status === 'fulfilled' ? lemmaInfo.value : null, synsets: synsets.status === 'fulfilled' ? synsets.value : [], error: [ lemmaInfo.status === 'rejected' ? String(lemmaInfo.reason?.message || lemmaInfo.reason) : '', synsets.status === 'rejected' ? String(synsets.reason?.message || synsets.reason) : '' ].filter(Boolean).join('; ') }; } export default { searchLemmas, getLemma, getLemmaSynsets, getLemmaRelations, getSynset, getSynsetLemmas, getSynsetRelations, searchSynsets, getSemfield, searchSemfields, lemmatize, translate, analyzeSentiment, lookupLemma };