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

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.

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>

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) {

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>

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": {

View File

@ -27,7 +27,9 @@
"webSearch": {
"provider": "联网搜索服务",
"providerHelp": "目前仅支持 Tavily每个月可以免费搜索 1000 次。\n请在 {0} 生成一个 API 密钥。",
"apiKey": "API 密钥"
"apiKey": "API 密钥",
"queryLanguage": "使用语言",
"queryLanguageHelp": "修改搜索词的语言。如果你想获取不同的搜索结果(比如查询高质量的英文资料),可以在这里修改。\nAI 模型在总结的时候仍然会使用当前网页的语言。"
}
},
"researchTopic": {

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)

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 +=

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 {