linguaielts-api / fronted /src /composables /useVocabCheck.js
AnhviNguyen
speaking practice
fbf0be8
Raw
History Blame Contribute Delete
4.13 kB
import { ref, computed, watch } from 'vue'
import { fetchLanguageAnalysis, clearLanguageAnalysisCache } from '@/services/speakingAnalysisService.js'
import { highlightTranscript } from '@/utils/highlightTranscript.js'
/**
* Vocabulary analysis from Whisper transcript via backend OpenRouter proxy.
* @param {{ transcript: import('vue').Ref<string>|string, questionText?: import('vue').Ref<string>|string, auto?: boolean }} options
*/
export function useVocabCheck(options = {}) {
const loading = ref(false)
const error = ref(null)
const score = ref(0)
const weakWords = ref([])
const strongWords = ref([])
const replacements = ref([])
const llmGenerated = ref(false)
const transcriptRef = options.transcript
const questionRef = options.questionText
const auto = options.auto !== false
function _val(maybeRef, fallback = '') {
if (maybeRef && typeof maybeRef === 'object' && 'value' in maybeRef) return maybeRef.value || fallback
return maybeRef || fallback
}
function applyVocabPayload(payload = {}) {
const va = payload.vocabulary_analysis || payload.vocabulary || {}
score.value = Number(va.score ?? payload.vocabulary_score ?? 0)
llmGenerated.value = Boolean(payload.llm_generated ?? va.llm_generated ?? false)
weakWords.value = (va.weak_words || []).map((w) => ({
text: w.text || w.word_used || w.word || '',
reason: w.reason || '',
}))
strongWords.value = (va.strong_words || []).map((w) => ({
text: w.text || w.word || '',
reason: w.reason || '',
}))
const reps = va.replacements || va.feedback || payload.vocabulary_feedback || []
replacements.value = reps.map((r) => ({
weak: r.weak || r.word_used || r.text || '',
better: r.better || r.better_alternative || '',
reason: r.reason || '',
}))
}
const highlightedHtml = computed(() => {
const transcript = _val(transcriptRef, '')
const spans = [
...weakWords.value.map((w) => ({
text: w.text,
className: 'hl-vocab-weak',
title: w.reason || 'weak / repetitive',
})),
...strongWords.value.map((w) => ({
text: w.text,
className: 'hl-vocab-strong',
title: w.reason || 'strong usage',
})),
]
return highlightTranscript(transcript, spans)
})
async function analyze(transcript, questionText = '') {
const t = transcript ?? _val(transcriptRef, '')
const q = questionText ?? _val(questionRef, '')
if (!t.trim()) {
error.value = 'No transcript to analyze.'
return null
}
loading.value = true
error.value = null
try {
const data = await fetchLanguageAnalysis({ transcript: t, questionText: q })
applyVocabPayload(data)
return data.vocabulary_analysis || data
} catch (err) {
const msg =
err.response?.data?.error ||
err.response?.data?.detail ||
err.message ||
'Vocabulary analysis failed.'
error.value = msg
return null
} finally {
loading.value = false
}
}
function hydrateFromEvaluate(result) {
if (!result) return
applyVocabPayload({
vocabulary_analysis: result.vocabulary_analysis,
vocabulary_feedback: result.vocabulary?.feedback,
vocabulary_score: result.vocabulary?.score ?? result.lexical_resource_score,
llm_generated: result.llm_generated,
})
llmGenerated.value = Boolean(result.llm_generated)
}
if (auto && transcriptRef && typeof transcriptRef === 'object' && 'value' in transcriptRef) {
watch(
() => [_val(transcriptRef), _val(questionRef)],
([t]) => {
if (
t &&
t.trim().length > 8 &&
!llmGenerated.value &&
!weakWords.value.length &&
!strongWords.value.length
) {
analyze(t, _val(questionRef))
}
},
{ immediate: false },
)
}
return {
loading,
error,
score,
weakWords,
strongWords,
replacements,
highlightedHtml,
llmGenerated,
analyze,
hydrateFromEvaluate,
clearCache: clearLanguageAnalysisCache,
}
}