feat: search in a different language

This commit is contained in:
AnotiaWang
2025-02-13 22:26:43 +08:00
parent 86c63d569b
commit 83f64b5948
9 changed files with 67 additions and 9 deletions
+2 -1
View File
@@ -1,12 +1,13 @@
# Deep Research Web UI
This is a web UI for https://github.com/dzhng/deep-research.
This is a web UI for https://github.com/dzhng/deep-research, with several improvements and fixes.
Features:
- **Realtime feedback**: Stream AI responses and reflect on the UI in real-time
- **Search visualization**: Shows the research process using a tree structure
- **Export as PDF**: Export the final research report as a PDF
- **Search in different languages**: Useful when you want to get search results in a different language
- **Bring Your Own API Key**: Everything (config, API requests, ...) happens in your browser
- **Supports more models**: Uses plain prompts instead of newer, less widely supported features like Structured Outputs. This ensures to work with more providers that haven't caught up with the latest OpenAI capabilities.
+15 -1
View File
@@ -137,7 +137,7 @@
<USeparator class="my-2" />
<!-- Web search provider -->
<!-- Web search -->
<h3 class="font-bold"> {{ $t('settings.webSearch.provider') }} </h3>
<UFormField>
<template #help>
@@ -164,6 +164,20 @@
:placeholder="$t('settings.webSearch.apiKey')"
/>
</UFormField>
<UFormField :label="$t('settings.webSearch.queryLanguage')">
<template #help>
<i18n-t
class="whitespace-pre-wrap"
keypath="settings.webSearch.queryLanguageHelp"
tag="p"
/>
</template>
<LangSwitcher
:value="config.webSearch.searchLanguage"
@update="config.webSearch.searchLanguage = $event"
private
/>
</UFormField>
</div>
</template>
<template #footer>
+5
View File
@@ -9,6 +9,7 @@
import { marked } from 'marked'
const { t, locale } = useI18n()
const { config } = storeToRefs(useConfigStore())
const emit = defineEmits<{
(e: 'complete', results: ResearchResult): void
}>()
@@ -146,11 +147,15 @@
searchResults.value = {}
isLoading.value = true
try {
const searchLanguage = config.value.webSearch.searchLanguage
? t('language', {}, { locale: config.value.webSearch.searchLanguage })
: undefined
await deepResearch({
query,
maxDepth: depth,
breadth,
language: t('language', {}, { locale: locale.value }),
searchLanguage,
onProgress: handleResearchProgress,
})
} catch (error) {
+23 -3
View File
@@ -1,17 +1,37 @@
<script setup lang="ts">
const { locale, availableLocales, t, setLocale } = useI18n()
const { locale: globalLocale, availableLocales, t, setLocale } = useI18n()
export type Locale = (typeof globalLocale)['value']
export type AvailableLocales = Locale[]
const props = defineProps<{
/** Override display locale */
value?: Locale
/** If true, it will not change global locales */
private?: boolean
}>()
const emit = defineEmits<{
(e: 'update', value: Locale): void
}>()
const localeOptions = availableLocales.map((locale) => ({
value: locale,
label: t('language', {}, { locale }),
}))
function changeLocale(l: Locale) {
emit('update', l)
if (props.private) return
setLocale(l)
}
</script>
<template>
<USelect
icon="i-lucide-languages"
:model-value="locale"
:model-value="value ?? globalLocale"
:items="localeOptions"
@update:model-value="setLocale($event)"
@update:model-value="changeLocale($event)"
/>
</template>
+3 -1
View File
@@ -27,7 +27,9 @@
"webSearch": {
"provider": "Web Search Provider",
"providerHelp": "Currently only supports Tavily. It provides lots of free quota (1000 credits / month).\nGet one API key at {0}.",
"apiKey": "API Key"
"apiKey": "API Key",
"queryLanguage": "Query Language",
"queryLanguageHelp": "The language of the search query. Useful if you want to get search results in a different language.\nWhen writing conclusions, the AI model will still use the language same as the web UI."
}
},
"researchTopic": {
+3 -1
View File
@@ -27,7 +27,9 @@
"webSearch": {
"provider": "联网搜索服务",
"providerHelp": "目前仅支持 Tavily,每个月可以免费搜索 1000 次。\n请在 {0} 生成一个 API 密钥。",
"apiKey": "API 密钥"
"apiKey": "API 密钥",
"queryLanguage": "使用语言",
"queryLanguageHelp": "修改搜索词的语言。如果你想获取不同的搜索结果(比如查询高质量的英文资料),可以在这里修改。\nAI 模型在总结的时候仍然会使用当前网页的语言。"
}
},
"researchTopic": {
+12 -1
View File
@@ -73,12 +73,15 @@ export function generateSearchQueries({
numQueries = 3,
learnings,
language,
searchLanguage,
}: {
query: string
language: string
numQueries?: number
// optional, if provided, the research will continue from the last learning
learnings?: string[]
/** Force the LLM to generate serp queries in a certain language */
searchLanguage?: string
}) {
const schema = z.object({
queries: z
@@ -95,7 +98,11 @@ export function generateSearchQueries({
.describe(`List of SERP queries, max of ${numQueries}`),
})
const jsonSchema = JSON.stringify(zodToJsonSchema(schema))
let lp = languagePrompt(language)
if (searchLanguage !== language) {
lp += `Use ${searchLanguage} for the SERP queries.`
}
const prompt = [
`Given the following prompt from the user, generate a list of SERP queries to research the topic. Return a maximum of ${numQueries} queries, but feel free to return less if the original prompt is clear. Make sure each query is unique and not similar to each other: <prompt>${query}</prompt>\n\n`,
learnings
@@ -104,7 +111,7 @@ export function generateSearchQueries({
)}`
: '',
`You MUST respond in JSON with the following schema: ${jsonSchema}`,
languagePrompt(language),
lp,
].join('\n\n')
return streamText({
model: useAiModel(),
@@ -213,6 +220,7 @@ export async function deepResearch({
onProgress,
currentDepth = 1,
nodeId = '0',
searchLanguage,
}: {
query: string
breadth: number
@@ -223,6 +231,8 @@ export async function deepResearch({
onProgress: (step: ResearchStep) => void
currentDepth?: number
nodeId?: string
/** Force the LLM to generate serp queries in a certain language */
searchLanguage?: string
}): Promise<ResearchResult> {
try {
const searchQueriesResult = generateSearchQueries({
@@ -230,6 +240,7 @@ export async function deepResearch({
learnings,
numQueries: breadth,
language,
searchLanguage,
})
const limit = pLimit(ConcurrencyLimit)
+1 -1
View File
@@ -20,7 +20,7 @@ export const systemPrompt = () => {
* @param language the language of the prompt, e.g. `English`
*/
export const languagePrompt = (language: string) => {
let languagePrompt = `- Respond in ${language}.`
let languagePrompt = `Respond in ${language}.`
if (language === '中文') {
languagePrompt +=
+3
View File
@@ -1,4 +1,5 @@
import { skipHydrate } from 'pinia'
import type { Locale } from '~/components/LangSwitcher.vue'
export type ConfigAiProvider = 'openai-compatible'
export interface ConfigAi {
@@ -11,6 +12,8 @@ export interface ConfigAi {
export interface ConfigWebSearch {
provider: 'tavily'
apiKey?: string
/** Force the LLM to generate serp queries in a certain language */
searchLanguage?: Locale
}
export interface Config {