1 line
16 KiB
JSON
1 line
16 KiB
JSON
{"repo": "anotiawang/deep-research-web-ui", "files": {"app/components/ConfigManager.vue": {"a83d650a990d30c4d7e8c60d77942fbb36e10306": {"t": 1785595434.188904, "n": 15005, "h": "8acb7b44c9143be8", "c": "<script setup lang=\"ts\">\n interface OpenAICompatibleModel {\n id: string\n object: string\n }\n interface OpenAICompatibleModelsResponse {\n object: string\n data: OpenAICompatibleModel[]\n }\n\n const {\n config,\n aiApiBase,\n webSearchApiBase,\n showConfigManager: showModal,\n } = storeToRefs(useConfigStore())\n const { t } = useI18n()\n const runtimeConfig = useRuntimeConfig()\n const isServerMode = computed(() => runtimeConfig.public.serverMode)\n\n const loadingAiModels = ref(false)\n const aiModelOptions = ref<string[]>([])\n /** If loading AI models failed, use a plain input to improve UX */\n const isLoadAiModelsFailed = ref(false)\n\n const activeSections = ref(['0', '1'])\n const settingSections = computed(() => [\n {\n label: t('settings.ai.provider'),\n icon: 'i-lucide-bot',\n slot: 'ai',\n },\n {\n label: t('settings.webSearch.provider'),\n icon: 'i-lucide-search',\n slot: 'web-search',\n },\n ])\n const aiProviderOptions = computed(() => [\n {\n label: t('settings.ai.providers.openaiCompatible.title'),\n help: 'settings.ai.providers.openaiCompatible.description',\n // Only kept for easy reference in i18n Ally\n _help: t('settings.ai.providers.openaiCompatible.description'),\n value: 'openai-compatible',\n },\n {\n label: t('settings.ai.providers.siliconflow.title'),\n help: 'settings.ai.providers.siliconflow.description',\n // Only kept for easy reference in i18n Ally\n _help: t('settings.ai.providers.siliconflow.description'),\n value: 'siliconflow',\n link: 'https://cloud.siliconflow.cn/i/J0NHrrX8',\n linkText: 'cloud.siliconflow.cn',\n },\n {\n label: t('settings.ai.providers.302-ai.title'),\n help: 'settings.ai.providers.302-ai.description',\n // Only kept for easy reference in i18n Ally\n _help: t('settings.ai.providers.302-ai.description'),\n value: '302-ai',\n link: 'https://share.302.ai/GqEnKu',\n linkText: '302.ai',\n },\n {\n label: t('settings.ai.providers.infiniai.title'),\n help: 'settings.ai.providers.infiniai.description',\n // Only kept for easy reference in i18n Ally\n _help: t('settings.ai.providers.infiniai.description'),\n value: 'infiniai',\n link: 'https://cloud.infini-ai.com/genstudio/model?deepsearch',\n linkText: 'cloud.infini-ai.com',\n },\n {\n label: 'DeepSeek',\n value: 'deepseek',\n },\n {\n label: 'OpenRouter',\n value: 'openrouter',\n },\n {\n label: 'Ollama',\n value: 'ollama',\n },\n ])\n const webSearchProviderOptions = computed(() => [\n {\n label: 'Tavily',\n value: 'tavily',\n help: 'settings.webSearch.providers.tavily.help',\n // Only kept for easy reference in i18n Ally\n _help: t('settings.webSearch.providers.tavily.help'),\n link: 'https://app.tavily.com/home',\n },\n {\n label: 'Firecrawl',\n value: 'firecrawl',\n help: 'settings.webSearch.providers.firecrawl.help',\n // Only kept for easy reference in i18n Ally\n _help: t('settings.webSearch.providers.firecrawl.help'),\n link: 'https://www.firecrawl.dev/app/api-keys',\n supportsCustomApiBase: true,\n },\n {\n label: 'fastCRW',\n value: 'crw',\n help: 'settings.webSearch.providers.crw.help',\n // Only kept for easy reference in i18n Ally\n _help: t('settings.webSearch.providers.crw.help'),\n link: 'https://fastcrw.com',\n supportsCustomApiBase: true,\n },\n {\n label: 'Google PSE',\n value: 'google-pse',\n help: 'settings.webSearch.providers.google-pse.help',\n link: 'https://programmablesearchengine.google.com/', // Link to Google PSE console\n },\n ])\n const tavilySearchTopicOptions = ['general', 'news', 'finance']\n const selectedAiProvider = computed(() =>\n aiProviderOptions.value.find((o) => o.value === config.value.ai.provider),\n )\n const selectedWebSearchProvider = computed(() =>\n webSearchProviderOptions.value.find((o) => o.value === config.value.webSearch.provider),\n )\n\n // Try to find available AI models based on selected provider\n const debouncedListAiModels = useDebounceFn(async () => {\n if (!aiApiBase.value || !aiApiBase.value.startsWith('http')) return\n\n try {\n loadingAiModels.value = true\n const result = await $fetch<OpenAICompatibleModelsResponse>(`${aiApiBase.value}/models`, {\n headers: {\n Authorization: `Bearer ${config.value.ai.apiKey}`,\n },\n })\n console.log(`Found ${result.data.length} AI models for provider ${config.value.ai.provider}`)\n aiModelOptions.value = result.data.map((m) => m.id)\n isLoadAiModelsFailed.value = false\n\n if (aiModelOptions.value.length) {\n // Auto-select the current model\n if (config.value.ai.model && !aiModelOptions.value.includes(config.value.ai.model)) {\n aiModelOptions.value.unshift(config.value.ai.model)\n }\n }\n } catch (error) {\n console.error(`Fetch AI models failed`, error)\n isLoadAiModelsFailed.value = true\n aiModelOptions.value = []\n } finally {\n loadingAiModels.value = false\n }\n }, 500)\n\n function createAndSelectAiModel(model: string) {\n aiModelOptions.value.push(model)\n config.value.ai.model = model\n }\n\n // Automatically fetch AI models list\n watch(\n () => [\n config.value.ai.provider,\n config.value.ai.apiKey,\n config.value.ai.apiBase,\n showModal.value,\n ],\n () => {\n if (!showModal.value || isServerMode.value) return\n debouncedListAiModels()\n },\n { immediate: true },\n )\n // Reset AI config when provider changed\n watch(\n () => config.value.ai.provider,\n () => {\n if (isServerMode.value) return\n config.value.ai.apiKey = ''\n config.value.ai.apiBase = ''\n config.value.ai.model = ''\n config.value.ai.contextSize = undefined\n aiModelOptions.value = []\n isLoadAiModelsFailed.value = false\n },\n )\n // Reset web search config when provider changed\n watch(\n () => config.value.webSearch.provider,\n () => {\n if (isServerMode.value) return\n config.value.webSearch.apiKey = ''\n config.value.webSearch.apiBase = ''\n },\n )\n\n defineExpose({\n show() {\n showModal.value = true\n },\n })\n</script>\n\n<template>\n <div>\n <UModal v-model:open=\"showModal\" :title=\"$t('settings.title')\">\n <UButton\n icon=\"i-lucide-settings\"\n :aria-label=\"$t('settings.title')\"\n :title=\"$t('settings.title')\"\n />\n\n <template #body>\n <UAccordion v-model=\"activeSections\" type=\"multiple\" :items=\"settingSections\" collapsible>\n <!-- AI provider -->\n <template #ai>\n <div class=\"flex flex-col gap-y-2 mb-2\">\n <UFormField>\n <template v-if=\"selectedAiProvider?.help\" #help>\n <i18n-t class=\"whitespace-pre-wrap\" :keypath=\"selectedAiProvider.help\" tag=\"span\">\n <UButton\n v-if=\"selectedAiProvider.link\"\n class=\"!p-0\"\n :to=\"selectedAiProvider.link\"\n target=\"_blank\"\n variant=\"link\"\n >\n {{ selectedAiProvider.linkText || selectedAiProvider.link }}\n </UButton>\n </i18n-t>\n </template>\n <USelect\n v-model=\"config.ai.provider\"\n class=\"w-full\"\n :items=\"aiProviderOptions\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n <UFormField\n :label=\"$t('settings.ai.apiKey')\"\n :required=\"config.ai.provider !== 'ollama'\"\n >\n <PasswordInput\n v-model=\"config.ai.apiKey\"\n class=\"w-full\"\n :placeholder=\"$t('settings.ai.apiKey')\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n <UFormField :label=\"$t('settings.ai.apiBase')\">\n <UInput\n v-model=\"config.ai.apiBase\"\n class=\"w-full\"\n :placeholder=\"aiApiBase\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n <UFormField :label=\"$t('settings.ai.model')\" required>\n <UInputMenu\n v-if=\"aiModelOptions.length && !isLoadAiModelsFailed && !isServerMode\"\n v-model=\"config.ai.model\"\n class=\"w-full\"\n :items=\"aiModelOptions\"\n :placeholder=\"$t('settings.ai.model')\"\n :loading=\"loadingAiModels\"\n create-item\n @create=\"createAndSelectAiModel\"\n :disabled=\"isServerMode\"\n />\n <UInput\n v-else\n v-model=\"config.ai.model\"\n class=\"w-full\"\n :placeholder=\"$t('settings.ai.model')\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n <UFormField :label=\"$t('settings.ai.contextSize')\">\n <template #help>\n {{ $t('settings.ai.contextSizeHelp') }}\n </template>\n <UInput\n v-model=\"config.ai.contextSize\"\n class=\"w-26\"\n type=\"number\"\n placeholder=\"128000\"\n :min=\"512\"\n :disabled=\"isServerMode\"\n />\n tokens\n </UFormField>\n </div>\n </template>\n\n <!-- Web search -->\n <template #web-search>\n <div class=\"flex flex-col gap-y-2\">\n <UFormField>\n <template #help>\n <i18n-t\n v-if=\"selectedWebSearchProvider?.help\"\n :keypath=\"selectedWebSearchProvider.help\"\n tag=\"p\"\n >\n <UButton\n class=\"!p-0\"\n :to=\"selectedWebSearchProvider.link\"\n target=\"_blank\"\n variant=\"link\"\n >\n {{ selectedWebSearchProvider.link }}\n </UButton>\n </i18n-t>\n </template>\n <USelect\n v-model=\"config.webSearch.provider\"\n class=\"w-full\"\n :items=\"webSearchProviderOptions\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n <UFormField\n :label=\"$t('settings.webSearch.apiKey')\"\n :required=\"!config.webSearch.apiBase\"\n >\n <PasswordInput\n v-model=\"config.webSearch.apiKey\"\n class=\"w-full\"\n :placeholder=\"$t('settings.webSearch.apiKey')\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n\n <template v-if=\"config.webSearch.provider === 'google-pse'\">\n <UFormField\n :label=\"$t('settings.webSearch.providers.google-pse.pseIdLabel')\"\n required\n >\n <UInput\n v-model=\"config.webSearch.googlePseId\"\n class=\"w-full\"\n :placeholder=\"$t('settings.webSearch.providers.google-pse.pseIdPlaceholder')\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n </template>\n\n <UFormField\n v-if=\"selectedWebSearchProvider?.supportsCustomApiBase\"\n :label=\"$t('settings.webSearch.apiBase')\"\n >\n <UInput\n v-model=\"config.webSearch.apiBase\"\n class=\"w-full\"\n :placeholder=\"webSearchApiBase\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n <UFormField :label=\"$t('settings.webSearch.queryLanguage')\">\n <template #help>\n <i18n-t\n class=\"whitespace-pre-wrap\"\n keypath=\"settings.webSearch.queryLanguageHelp\"\n tag=\"p\"\n />\n </template>\n <LangSwitcher\n :value=\"config.webSearch.searchLanguage\"\n @update=\"config.webSearch.searchLanguage = $event\"\n private\n :disabled=\"isServerMode\"\n />\n </UFormField>\n <UFormField :label=\"$t('settings.webSearch.concurrencyLimit')\">\n <template #help>\n {{ $t('settings.webSearch.concurrencyLimitHelp') }}\n </template>\n <UInput\n v-model=\"config.webSearch.concurrencyLimit\"\n class=\"w-15\"\n type=\"number\"\n placeholder=\"2\"\n :min=\"1\"\n :max=\"5\"\n :step=\"1\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n\n <!-- Tavily-specific settings -->\n <template v-if=\"config.webSearch.provider === 'tavily'\">\n <UFormField\n :label=\"$t('settings.webSearch.providers.tavily.advancedSearch')\"\n :help=\"$t('settings.webSearch.providers.tavily.advancedSearchHelp')\"\n >\n <USwitch\n v-model=\"config.webSearch.tavilyAdvancedSearch\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n <UFormField\n :label=\"$t('settings.webSearch.providers.tavily.searchTopic')\"\n :help=\"$t('settings.webSearch.providers.tavily.searchTopicHelp')\"\n >\n <USelect\n v-model=\"config.webSearch.tavilySearchTopic\"\n class=\"w-30\"\n :items=\"tavilySearchTopicOptions\"\n placeholder=\"general\"\n :disabled=\"isServerMode\"\n />\n </UFormField>\n </template>\n </div>\n </template>\n </UAccordion>\n </template>\n\n <template #footer>\n <div class=\"flex items-center justify-between gap-2 w-full\">\n <p class=\"text-sm text-gray-500\">\n {{ isServerMode ? $t('serverMode.configNotice') : $t('settings.disclaimer') }}\n </p>\n <UButton\n v-if=\"!isServerMode\"\n color=\"primary\"\n icon=\"i-lucide-check\"\n @click=\"showModal = false\"\n >\n {{ $t('settings.save') }}\n </UButton>\n </div>\n </template>\n </UModal>\n </div>\n</template>\n"}}}} |