160 lines
4.3 KiB
Vue
160 lines
4.3 KiB
Vue
<script setup lang="ts">
|
|
import {
|
|
feedbackInjectionKey,
|
|
formInjectionKey,
|
|
} from '~/constants/injection-keys'
|
|
import { generateFeedback } from '~/lib/feedback'
|
|
|
|
export interface ResearchFeedbackResult {
|
|
assistantQuestion: string
|
|
userAnswer: string
|
|
}
|
|
|
|
const props = defineProps<{
|
|
isLoadingSearch?: boolean
|
|
}>()
|
|
|
|
defineEmits<{
|
|
(e: 'submit'): void
|
|
}>()
|
|
|
|
const { t, locale } = useI18n()
|
|
const { config, showConfigManager } = storeToRefs(useConfigStore())
|
|
const toast = useToast()
|
|
|
|
const reasoningContent = ref('')
|
|
const isLoading = ref(false)
|
|
const error = ref('')
|
|
|
|
// Inject global data from index.vue
|
|
const form = inject(formInjectionKey)!
|
|
const feedback = inject(feedbackInjectionKey)!
|
|
|
|
const isSubmitButtonDisabled = computed(
|
|
() =>
|
|
!feedback.value.length ||
|
|
// All questions should be answered
|
|
feedback.value.some((v) => !v.assistantQuestion || !v.userAnswer) ||
|
|
// Should not be loading
|
|
isLoading.value ||
|
|
props.isLoadingSearch,
|
|
)
|
|
|
|
async function getFeedback() {
|
|
const aiConfig = config.value.ai
|
|
const webSearchConfig = config.value.webSearch
|
|
|
|
if (!aiConfig.model || !aiConfig.apiKey || !webSearchConfig.apiKey) {
|
|
toast.add({
|
|
title: t('index.missingConfigTitle'),
|
|
description: t('index.missingConfigDescription'),
|
|
color: 'error',
|
|
})
|
|
showConfigManager.value = true
|
|
return
|
|
}
|
|
clear()
|
|
isLoading.value = true
|
|
try {
|
|
for await (const f of generateFeedback({
|
|
query: form.value.query,
|
|
numQuestions: form.value.numQuestions,
|
|
language: t('language', {}, { locale: locale.value }),
|
|
})) {
|
|
if (f.type === 'reasoning') {
|
|
reasoningContent.value += f.delta
|
|
} else if (f.type === 'error') {
|
|
error.value = f.message
|
|
} else if (f.type === 'object') {
|
|
const questions = f.value.questions!.filter(
|
|
(s) => typeof s === 'string',
|
|
)
|
|
// Incrementally update modelValue
|
|
for (let i = 0; i < questions.length; i += 1) {
|
|
if (feedback.value[i]) {
|
|
feedback.value[i].assistantQuestion = questions[i]
|
|
} else {
|
|
feedback.value.push({
|
|
assistantQuestion: questions[i],
|
|
userAnswer: '',
|
|
})
|
|
}
|
|
}
|
|
} else if (f.type === 'bad-end') {
|
|
error.value = t('invalidStructuredOutput')
|
|
}
|
|
}
|
|
console.log(
|
|
`[ResearchFeedback] query: ${form.value.query}, feedback:`,
|
|
feedback.value,
|
|
)
|
|
// Check if model returned questions
|
|
if (!feedback.value.length) {
|
|
error.value = t('modelFeedback.noQuestions')
|
|
}
|
|
} catch (e: any) {
|
|
console.error('Error getting feedback:', e)
|
|
if (e.message.includes('Failed to fetch')) {
|
|
e.message += `\n${t('error.requestBlockedByCORS')}`
|
|
}
|
|
error.value = t('modelFeedback.error', [e.message])
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
function clear() {
|
|
feedback.value = []
|
|
error.value = ''
|
|
reasoningContent.value = ''
|
|
}
|
|
|
|
defineExpose({
|
|
getFeedback,
|
|
clear,
|
|
isLoading,
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<UCard>
|
|
<template #header>
|
|
<h2 class="font-bold">{{ $t('modelFeedback.title') }}</h2>
|
|
<p class="text-sm text-gray-500">
|
|
{{ $t('modelFeedback.description') }}
|
|
</p>
|
|
</template>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<div v-if="!feedback.length && !reasoningContent && !error">
|
|
{{ $t('modelFeedback.waiting') }}
|
|
</div>
|
|
<template v-else>
|
|
<div v-if="error" class="text-red-500 whitespace-pre-wrap">
|
|
{{ error }}
|
|
</div>
|
|
|
|
<ReasoningAccordion v-model="reasoningContent" :loading="isLoading" />
|
|
|
|
<div
|
|
v-for="(feedback, index) in feedback"
|
|
class="flex flex-col gap-2"
|
|
:key="index"
|
|
>
|
|
{{ feedback.assistantQuestion }}
|
|
<UInput v-model="feedback.userAnswer" />
|
|
</div>
|
|
</template>
|
|
<UButton
|
|
color="primary"
|
|
:loading="isLoadingSearch || isLoading"
|
|
:disabled="isSubmitButtonDisabled"
|
|
block
|
|
@click="$emit('submit')"
|
|
>
|
|
{{ $t('modelFeedback.submit') }}
|
|
</UButton>
|
|
</div>
|
|
</UCard>
|
|
</template>
|