feat: Improve web search UI and localization

This commit is contained in:
kangfenmao 2025-02-23 14:05:47 +08:00
parent af9763d142
commit cacd0a1387
9 changed files with 75 additions and 60 deletions

View File

@ -101,6 +101,9 @@
"input.upload": "Upload image or document file",
"input.upload.document": "Upload document file (model does not support images)",
"input.web_search": "Enable web search",
"input.web_search.enable": "Enable web search",
"input.web_search.enable_content": "Enable web search in Settings",
"input.web_search.button.ok": "Go to Settings",
"message.new.branch": "New Branch",
"message.new.branch.created": "New Branch Created",
"message.new.context": "New Context",
@ -403,7 +406,8 @@
"upgrade.success.title": "Upgrade successfully",
"warn.notion.exporting": "Exporting to Notion, please do not request export repeatedly!",
"searching": "Searching the internet...",
"ignore.knowledge.base": "Web search mode is enabled, ignore knowledge base"
"ignore.knowledge.base": "Web search mode is enabled, ignore knowledge base",
"info.notion.block_reach_limit": "Dialogue too long, exporting to Notion in pages"
},
"minapp": {
"sidebar.add.title": "Add to sidebar",
@ -594,11 +598,11 @@
"notion.api_key_placeholder": "Enter Notion API Key",
"notion.check": {
"button": "Check",
"empty_api_key": "Api_key is not configured",
"empty_database_id": "Database_id is not configured",
"error": "Connection error, please check network configuration and Api_key and Database_id",
"fail": "Connection failed, please check network and Api_key and Database_id",
"success": "Connection successful"
"success": "Connection successful",
"error": "Connection error, please check network configuration and Api_key and Database_id",
"empty_api_key": "Api_key is not configured",
"empty_database_id": "Database_id is not configured"
},
"notion.database_id": "Notion Database ID",
"notion.database_id_placeholder": "Enter Notion Database ID",
@ -606,15 +610,6 @@
"notion.page_name_key": "Page Title Field Name",
"notion.page_name_key_placeholder": "Enter page title field name, default is Name",
"notion.title": "Notion Configuration",
"notion.help": "Notion Configuration Documentation",
"notion.check": {
"button": "Check",
"fail": "Connection failed, please check network and Api_key and Database_id",
"success": "Connection successful",
"error": "Connection error, please check network configuration and Api_key and Database_id",
"empty_api_key": "Api_key is not configured",
"empty_database_id": "Database_id is not configured"
},
"notion.auto_split": "Auto split when exporting",
"notion.auto_split_tip": "Automatically split pages when exporting long topics to Notion",
"notion.split_size": "Split size",

View File

@ -101,6 +101,9 @@
"input.upload": "画像またはドキュメントをアップロード",
"input.upload.document": "ドキュメントをアップロード(モデルは画像をサポートしません)",
"input.web_search": "ウェブ検索を有効にする",
"input.web_search.enable": "ウェブ検索を有効にする",
"input.web_search.enable_content": "ウェブ検索を有効にするには、設定でウェブ検索を有効にする必要があります",
"input.web_search.button.ok": "設定に移動",
"message.new.branch": "新しいブランチ",
"message.new.branch.created": "新しいブランチが作成されました",
"message.new.context": "新しいコンテキスト",
@ -403,7 +406,8 @@
"upgrade.success.title": "アップグレードに成功しました",
"warn.notion.exporting": "Notionにエクスポート中です。重複してエクスポートしないでください! ",
"searching": "インターネットで検索中...",
"ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します"
"ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します",
"info.notion.block_reach_limit": "会話が長すぎます。Notionにページごとにエクスポートしています"
},
"minapp": {
"sidebar.add.title": "サイドバーに追加",
@ -594,11 +598,11 @@
"notion.api_key_placeholder": "Notion APIキーを入力してください",
"notion.check": {
"button": "確認",
"empty_api_key": "Api_keyが設定されていません",
"empty_database_id": "Database_idが設定されていません",
"error": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください",
"fail": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください",
"success": "接続に成功しました。"
"success": "接続に成功しました。",
"error": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください",
"empty_api_key": "Api_keyが設定されていません",
"empty_database_id": "Database_idが設定されていません"
},
"notion.database_id": "Notion データベースID",
"notion.database_id_placeholder": "Notion データベースIDを入力してください",
@ -606,15 +610,6 @@
"notion.page_name_key": "ページタイトルフィールド名",
"notion.page_name_key_placeholder": "ページタイトルフィールド名を入力してください。デフォルトは Name です",
"notion.title": "Notion 設定",
"notion.help": "Notion 設定ドキュメント",
"notion.check": {
"button": "確認",
"fail": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください",
"success": "接続に成功しました。",
"error": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください",
"empty_api_key": "Api_keyが設定されていません",
"empty_database_id": "Database_idが設定されていません"
},
"notion.auto_split": "내보내기 시 자동 분할",
"notion.auto_split_tip": "긴 주제를 Notion으로 내보낼 때 자동으로 페이지 분할",
"notion.split_size": "분할 크기",

View File

@ -101,6 +101,9 @@
"input.upload": "Загрузить изображение или документ",
"input.upload.document": "Загрузить документ (модель не поддерживает изображения)",
"input.web_search": "Включить веб-поиск",
"input.web_search.enable": "Включить веб-поиск",
"input.web_search.enable_content": "Необходимо включить веб-поиск в Настройки",
"input.web_search.button.ok": "Перейти в Настройки",
"message.new.branch": "Новая ветка",
"message.new.branch.created": "Новая ветка создана",
"message.new.context": "Новый контекст",
@ -403,7 +406,8 @@
"upgrade.success.title": "Обновление успешно",
"warn.notion.exporting": "Экспортируется в Notion, пожалуйста, не отправляйте повторные запросы!",
"searching": "Поиск в Интернете...",
"ignore.knowledge.base": "Режим сети включен, игнорировать базу знаний"
"ignore.knowledge.base": "Режим сети включен, игнорировать базу знаний",
"info.notion.block_reach_limit": "Диалог слишком длинный, экспортируется в Notion по страницам"
},
"minapp": {
"sidebar.add.title": "Добавить в боковую панель",
@ -629,7 +633,12 @@
"syncStatus": "Статус резервного копирования",
"title": "WebDAV",
"user": "Пользователь WebDAV"
}
},
"notion.auto_split_tip": "Автоматическое разбиение на страницы при экспорте в Notion, если тема слишком длинная",
"notion.auto_split": "Автоматическое разбиение на страницы при экспорте диалога",
"notion.split_size": "Размер автоматического разбиения",
"notion.split_size_placeholder": "Введите ограничение количества блоков на странице (по умолчанию 90)",
"notion.split_size_help": "Рекомендуется 90 для пользователей бесплатной версии Notion, 24990 для пользователей премиум-версии, значение по умолчанию — 90"
},
"display.custom.css": "Пользовательский CSS",
"display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */",

View File

@ -101,6 +101,9 @@
"input.upload": "上传图片或文档",
"input.upload.document": "上传文档(模型不支持图片)",
"input.web_search": "开启网络搜索",
"input.web_search.enable": "开启网络搜索",
"input.web_search.enable_content": "需要先在设置中开启网络搜索",
"input.web_search.button.ok": "去设置",
"message.new.branch": "分支",
"message.new.branch.created": "新分支已创建",
"message.new.context": "清除上下文",

View File

@ -101,6 +101,9 @@
"input.upload": "上傳圖片或文檔",
"input.upload.document": "上傳文檔(模型不支持圖片)",
"input.web_search": "開啟網路搜索",
"input.web_search.enable": "開啟網路搜索",
"input.web_search.enable_content": "需要先在設定中開啟網路搜索",
"input.web_search.button.ok": "去設定",
"message.new.branch": "分支",
"message.new.branch.created": "新分支已建立",
"message.new.context": "新上下文",
@ -403,7 +406,8 @@
"upgrade.success.title": "升級成功",
"warn.notion.exporting": "正在導出到Notion請勿重複請求導出",
"searching": "正在網路搜索...",
"ignore.knowledge.base": "網路模式開啟,忽略知識庫"
"ignore.knowledge.base": "網路模式開啟,忽略知識庫",
"info.notion.block_reach_limit": "對話過長自動分頁導出到Notion"
},
"minapp": {
"sidebar.add.title": "添加到側邊欄",
@ -594,11 +598,11 @@
"notion.api_key_placeholder": "請輸入Notion 密鑰",
"notion.check": {
"button": "檢查",
"empty_api_key": "未配置Api_key",
"empty_database_id": "未配置Database_id",
"error": "連接異常請檢查網絡及Api_key和Database_id是否正確",
"fail": "連接失敗請檢查網絡及Api_key和Database_id是否正確",
"success": "連線成功"
"success": "連線成功",
"error": "連接異常請檢查網絡及Api_key和Database_id是否正確",
"empty_api_key": "未配置Api_key",
"empty_database_id": "未配置Database_id"
},
"notion.database_id": "Notion 資料庫 ID",
"notion.database_id_placeholder": "請輸入Notion 資料庫 ID",
@ -606,15 +610,6 @@
"notion.page_name_key": "頁面標題欄位名稱",
"notion.page_name_key_placeholder": "請輸入頁面標題欄位名稱,預設為 Name",
"notion.title": "Notion 配置",
"notion.help": "Notion 配置文檔",
"notion.check": {
"button": "檢查",
"fail": "連接失敗請檢查網絡及Api_key和Database_id是否正確",
"success": "連線成功",
"error": "連接異常請檢查網絡及Api_key和Database_id是否正確",
"empty_api_key": "未配置Api_key",
"empty_database_id": "未配置Database_id"
},
"notion.auto_split": "導出對話時自動分頁",
"notion.auto_split_tip": "當要導出的話題過長時自動分頁導出到Notion",
"notion.split_size": "自動分頁大小",

View File

@ -34,6 +34,7 @@ import dayjs from 'dayjs'
import { debounce, isEmpty } from 'lodash'
import { CSSProperties, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import NarrowLayout from '../Messages/NarrowLayout'
@ -87,6 +88,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
const currentMessageId = useRef<string>()
const isVision = useMemo(() => isVisionModel(model), [model])
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
const navigate = useNavigate()
const showKnowledgeIcon = useSidebarIconShow('knowledge')
@ -498,6 +500,23 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
setMentionModels(mentionModels.filter((m) => m.id !== model.id))
}
const onEnableWebSearch = () => {
if (!WebSearchService.isWebSearchEnabled()) {
window.modal.confirm({
title: t('chat.input.web_search.enable'),
content: t('chat.input.web_search.enable_content'),
centered: true,
okText: t('chat.input.web_search.button.ok'),
onOk: () => {
navigate('/settings/web-search')
}
})
return
}
updateAssistant({ ...assistant, enableWebSearch: !assistant.enableWebSearch })
}
return (
<Container onDragOver={handleDragOver} onDrop={handleDrop} className="inputbar">
<NarrowLayout style={{ width: '100%' }}>
@ -546,17 +565,13 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
onMentionModel={onMentionModel}
ToolbarButton={ToolbarButton}
/>
{WebSearchService.isWebSearchEnabled() && (
<Tooltip placement="top" title={t('chat.input.web_search')} arrow>
<ToolbarButton
type="text"
onClick={() => updateAssistant({ ...assistant, enableWebSearch: !assistant.enableWebSearch })}>
<GlobalOutlined
style={{ color: assistant.enableWebSearch ? 'var(--color-link)' : 'var(--color-icon)' }}
/>
</ToolbarButton>
</Tooltip>
)}
<Tooltip placement="top" title={t('chat.input.web_search')} arrow>
<ToolbarButton type="text" onClick={onEnableWebSearch}>
<GlobalOutlined
style={{ color: assistant.enableWebSearch ? 'var(--color-link)' : 'var(--color-icon)' }}
/>
</ToolbarButton>
</Tooltip>
<Tooltip placement="top" title={t('chat.input.clear', { Command: cleanTopicShortcut })} arrow>
<Popconfirm
title={t('chat.input.clear.content')}

View File

@ -14,6 +14,7 @@ import styled from 'styled-components'
import Markdown from '../Markdown/Markdown'
import MessageAttachments from './MessageAttachments'
import MessageError from './MessageError'
import MessageSearchResults from './MessageSearchResults'
import MessageThought from './MessageThought'
interface Props {
@ -111,6 +112,7 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
)}
</Fragment>
)}
<MessageSearchResults message={message} />
{formattedCitations && (
<CitationsContainer>
<CitationsTitle>
@ -134,7 +136,9 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
<HStack key={result.url} style={{ alignItems: 'center', gap: 8 }}>
<span style={{ fontSize: 13, color: 'var(--color-text-2)' }}>{index + 1}.</span>
<Favicon src={`https://icon.horse/icon/${new URL(result.url).hostname}`} alt={result.title} />
<CitationLink>{result.title}</CitationLink>
<CitationLink href={result.url} target="_blank" rel="noopener noreferrer">
{result.title}
</CitationLink>
</HStack>
))}
</CitationsContainer>

View File

@ -89,8 +89,7 @@ const Link = styled.a`
`
const SearchEntryPoint = styled.div`
margin-top: 10px;
margin-bottom: 10px;
margin: 10px 2px;
`
export default MessageSearchResults

View File

@ -5,7 +5,7 @@ import { setGenerating } from '@renderer/store/runtime'
import { Assistant, Message, Model, Provider, Suggestion } from '@renderer/types'
import { addAbortController } from '@renderer/utils/abortController'
import { formatMessageError } from '@renderer/utils/error'
import { isEmpty, last } from 'lodash'
import { findLast, isEmpty } from 'lodash'
import AiProvider from '../providers/AiProvider'
import {
@ -51,13 +51,13 @@ export async function fetchChatCompletion({
try {
let _messages: Message[] = []
let isFirstChunk = true
const lastMessage = last(messages)
// Search web
if (WebSearchService.isWebSearchEnabled() && assistant.enableWebSearch && assistant.model) {
const webSearchParams = getOpenAIWebSearchParams(assistant, assistant.model)
if (isEmpty(webSearchParams)) {
const lastMessage = findLast(messages, (m) => m.role === 'user')
const hasKnowledgeBase = !isEmpty(lastMessage?.knowledgeBaseIds)
if (lastMessage) {
if (hasKnowledgeBase) {