From 74528ddca740e7aec2b1f12d6767255963b78381 Mon Sep 17 00:00:00 2001 From: AnotiaWang Date: Sat, 15 Feb 2025 12:14:31 +0800 Subject: [PATCH] feat: support regenerating research report --- components/AutoUpdateToast.vue | 2 + components/ConfigManager.vue | 7 ++- components/DeepResearch.vue | 41 +++++++++++--- components/ResearchFeedback.vue | 39 +++++++++++--- components/ResearchForm.vue | 31 ++++++----- components/ResearchReport.vue | 95 ++++++++++++++++++++------------- constants/injection-keys.ts | 11 ++++ i18n/en.json | 3 +- i18n/zh.json | 3 +- pages/index.vue | 56 +++++++++---------- public/version.json | 2 +- stores/config.ts | 4 +- utils/prompt.ts | 14 +++++ 13 files changed, 202 insertions(+), 106 deletions(-) create mode 100644 constants/injection-keys.ts create mode 100644 utils/prompt.ts diff --git a/components/AutoUpdateToast.vue b/components/AutoUpdateToast.vue index dab7fdf..2ea9a4a 100644 --- a/components/AutoUpdateToast.vue +++ b/components/AutoUpdateToast.vue @@ -63,3 +63,5 @@ { immediate: true }, ) + + diff --git a/components/ConfigManager.vue b/components/ConfigManager.vue index c08fe02..593838b 100644 --- a/components/ConfigManager.vue +++ b/components/ConfigManager.vue @@ -8,10 +8,13 @@ data: OpenAICompatibleModel[] } - const { config, aiApiBase } = storeToRefs(useConfigStore()) + const { + config, + aiApiBase, + showConfigManager: showModal, + } = storeToRefs(useConfigStore()) const { t } = useI18n() - const showModal = ref(false) const loadingAiModels = ref(false) const aiModelOptions = ref([]) /** If loading AI models failed, use a plain input to improve UX */ diff --git a/components/DeepResearch.vue b/components/DeepResearch.vue index 272d225..4faa75e 100644 --- a/components/DeepResearch.vue +++ b/components/DeepResearch.vue @@ -2,14 +2,18 @@ import { deepResearch, type PartialSearchResult, - type ResearchResult, type ResearchStep, } from '~/lib/deep-research' import type { TreeNode } from './Tree.vue' import { marked } from 'marked' + import { + feedbackInjectionKey, + formInjectionKey, + researchResultInjectionKey, + } from '~/constants/injection-keys' const emit = defineEmits<{ - (e: 'complete', results: ResearchResult): void + (e: 'complete'): void }>() const toast = useToast() @@ -25,6 +29,11 @@ const searchResults = ref>({}) const isLoading = ref(false) + // Inject global data from index.vue + const form = inject(formInjectionKey)! + const feedback = inject(feedbackInjectionKey)! + const completeResult = inject(researchResultInjectionKey)! + function handleResearchProgress(step: ResearchStep) { let node: TreeNode | null = null let nodeId = '' @@ -80,14 +89,17 @@ } case 'generated_query': { + console.log(`[DeepResearch] node ${nodeId} generated query:`, step) break } case 'searching': { + console.log(`[DeepResearch] node ${nodeId} searching:`, step) break } case 'search_complete': { + console.log(`[DeepResearch] node ${nodeId} search complete:`, step) if (node) { node.visitedUrls = step.urls } @@ -110,6 +122,10 @@ } case 'processed_search_result': { + console.log( + `[DeepResearch] node ${nodeId} processed_search_result:`, + step, + ) if (node) { node.learnings = step.result.learnings searchResults.value[nodeId] = step.result @@ -118,7 +134,7 @@ } case 'error': - console.error(`Research error on node ${nodeId}:`, step.message) + console.error(`[DeepResearch] node ${nodeId} error:`, step.message) node!.error = step.message toast.add({ title: t('webBrowsing.nodeFailedToast', { @@ -131,7 +147,12 @@ break case 'complete': - emit('complete', step) + console.log(`[DeepResearch] complete:`, step) + completeResult.value = { + learnings: step.learnings, + visitedUrls: step.visitedUrls, + } + emit('complete') isLoading.value = false break } @@ -167,7 +188,9 @@ return parts.join('-') } - async function startResearch(query: string, depth: number, breadth: number) { + async function startResearch() { + if (!form.value.query || !form.value.breadth || !form.value.depth) return + tree.value.children = [] selectedNode.value = undefined searchResults.value = {} @@ -179,9 +202,9 @@ ? t('language', {}, { locale: config.value.webSearch.searchLanguage }) : undefined await deepResearch({ - query, - maxDepth: depth, - breadth, + query: getCombinedQuery(form.value, feedback.value), + maxDepth: form.value.depth, + breadth: form.value.breadth, language: t('language', {}, { locale: locale.value }), searchLanguage, onProgress: handleResearchProgress, @@ -276,7 +299,9 @@

+ import { + feedbackInjectionKey, + formInjectionKey, + } from '~/constants/injection-keys' import { generateFeedback } from '~/lib/feedback' export interface ResearchFeedbackResult { @@ -11,16 +15,21 @@ }>() defineEmits<{ - (e: 'submit', feedback: ResearchFeedbackResult[]): void + (e: 'submit'): void }>() const { t, locale } = useI18n() - const reasoningContent = ref('') - const feedback = ref([]) + 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 || @@ -31,13 +40,25 @@ props.isLoadingSearch, ) - async function getFeedback(query: string, numQuestions = 3) { + 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, - numQuestions, + query: form.value.query, + numQuestions: form.value.numQuestions, language: t('language', {}, { locale: locale.value }), })) { if (f.type === 'reasoning') { @@ -63,6 +84,10 @@ 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') @@ -125,7 +150,7 @@ :loading="isLoadingSearch || isLoading" :disabled="isSubmitButtonDisabled" block - @click="$emit('submit', feedback)" + @click="$emit('submit')" > {{ $t('modelFeedback.submit') }} diff --git a/components/ResearchForm.vue b/components/ResearchForm.vue index 99c4611..607e844 100644 --- a/components/ResearchForm.vue +++ b/components/ResearchForm.vue @@ -1,4 +1,6 @@ diff --git a/components/ResearchReport.vue b/components/ResearchReport.vue index 3d09ee9..ea1b936 100644 --- a/components/ResearchReport.vue +++ b/components/ResearchReport.vue @@ -1,14 +1,12 @@ diff --git a/public/version.json b/public/version.json index 688e939..578757c 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "1.0.0" + "version": "1.0.4" } \ No newline at end of file diff --git a/stores/config.ts b/stores/config.ts index 339915c..d4ef11d 100644 --- a/stores/config.ts +++ b/stores/config.ts @@ -37,5 +37,7 @@ export const useConfigStore = defineStore('config', () => { return config.value.ai.apiBase || 'https://api.openai.com/v1' }) - return { config: skipHydrate(config), aiApiBase } + const showConfigManager = ref(false) + + return { config: skipHydrate(config), aiApiBase, showConfigManager } }) diff --git a/utils/prompt.ts b/utils/prompt.ts new file mode 100644 index 0000000..9448bde --- /dev/null +++ b/utils/prompt.ts @@ -0,0 +1,14 @@ +import type { ResearchFeedbackResult } from '~/components/ResearchFeedback.vue' +import type { ResearchInputData } from '~/components/ResearchForm.vue' + +export function getCombinedQuery( + form: ResearchInputData, + feedback: ResearchFeedbackResult[], +) { + return `Initial Query: ${form.query} +Follow-up Questions and Answers: +${feedback + .map((qa) => `Q: ${qa.assistantQuestion}\nA: ${qa.userAnswer}`) + .join('\n')} + ` +}