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