mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 14:31:35 +08:00
Merge branch 'feat/ocr' into feat/ocr-translate
This commit is contained in:
commit
222b3bacab
@ -228,6 +228,7 @@
|
||||
"proxy-agent": "^6.5.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
|
||||
@ -68,7 +68,11 @@ class CopilotService {
|
||||
|
||||
constructor() {
|
||||
this.tokenFilePath = path.join(app.getPath('userData'), '.copilot_token')
|
||||
this.headers = { ...CONFIG.DEFAULT_HEADERS }
|
||||
this.headers = {
|
||||
...CONFIG.DEFAULT_HEADERS,
|
||||
accept: 'application/json',
|
||||
'user-agent': 'Visual Studio Code (desktop)'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,6 +97,7 @@ class CopilotService {
|
||||
'Sec-Fetch-Site': 'none',
|
||||
'Sec-Fetch-Mode': 'no-cors',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
accept: 'application/json',
|
||||
authorization: `token ${token}`
|
||||
}
|
||||
})
|
||||
|
||||
@ -204,7 +204,7 @@ export function registerShortcuts(window: BrowserWindow) {
|
||||
selectionAssistantSelectTextAccelerator = formatShortcutKey(shortcut.shortcut)
|
||||
break
|
||||
|
||||
//the following ZOOMs will register shortcuts seperately, so will return
|
||||
//the following ZOOMs will register shortcuts separately, so will return
|
||||
case 'zoom_in':
|
||||
globalShortcut.register('CommandOrControl+=', () => handler(window))
|
||||
globalShortcut.register('CommandOrControl+numadd', () => handler(window))
|
||||
|
||||
@ -555,9 +555,9 @@ export class WindowService {
|
||||
|
||||
// [Windows] hacky fix
|
||||
// the window is minimized only when in Windows platform
|
||||
// because it's a workround for Windows, see `hideMiniWindow()`
|
||||
// because it's a workaround for Windows, see `hideMiniWindow()`
|
||||
if (this.miniWindow?.isMinimized()) {
|
||||
// don't let the window being seen before we finish adusting the position across screens
|
||||
// don't let the window being seen before we finish adjusting the position across screens
|
||||
this.miniWindow?.setOpacity(0)
|
||||
// DO NOT use `restore()` here, Electron has the bug with screens of different scale factor
|
||||
// We have to use `show()` here, then set the position and bounds
|
||||
|
||||
@ -4,6 +4,7 @@ import { FC, useMemo } from 'react'
|
||||
import { HashRouter, Route, Routes } from 'react-router-dom'
|
||||
|
||||
import Sidebar from './components/app/Sidebar'
|
||||
import { ErrorBoundary } from './components/ErrorBoundary'
|
||||
import TabsContainer from './components/Tab/TabContainer'
|
||||
import NavigationHandler from './handler/NavigationHandler'
|
||||
import { useNavbarPosition } from './hooks/useSettings'
|
||||
@ -23,18 +24,20 @@ const Router: FC = () => {
|
||||
|
||||
const routes = useMemo(() => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/agents" element={<AgentsPage />} />
|
||||
<Route path="/paintings/*" element={<PaintingsRoutePage />} />
|
||||
<Route path="/translate" element={<TranslatePage />} />
|
||||
<Route path="/files" element={<FilesPage />} />
|
||||
<Route path="/knowledge" element={<KnowledgePage />} />
|
||||
<Route path="/apps" element={<MinAppsPage />} />
|
||||
<Route path="/code" element={<CodeToolsPage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
<Route path="/launchpad" element={<LaunchpadPage />} />
|
||||
</Routes>
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/agents" element={<AgentsPage />} />
|
||||
<Route path="/paintings/*" element={<PaintingsRoutePage />} />
|
||||
<Route path="/translate" element={<TranslatePage />} />
|
||||
<Route path="/files" element={<FilesPage />} />
|
||||
<Route path="/knowledge" element={<KnowledgePage />} />
|
||||
<Route path="/apps" element={<MinAppsPage />} />
|
||||
<Route path="/code" element={<CodeToolsPage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
<Route path="/launchpad" element={<LaunchpadPage />} />
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
@ -157,6 +157,7 @@ const IconWrapper = styled.div<{ $isStreaming: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: ${(props) =>
|
||||
@ -177,13 +178,16 @@ const TitleSection = styled.div`
|
||||
gap: 6px;
|
||||
`
|
||||
|
||||
const Title = styled.h3`
|
||||
margin: 0 !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
const Title = styled.span`
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: var(--color-text-1);
|
||||
line-height: 1.4;
|
||||
font-family: 'Ubuntu';
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const TypeBadge = styled.div`
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import CodeEditor, { CodeEditorHandles } from '@renderer/components/CodeEditor'
|
||||
import { isLinux, isMac, isWin } from '@renderer/config/constant'
|
||||
import { classNames } from '@renderer/utils'
|
||||
import { Button, Modal, Splitter, Tooltip } from 'antd'
|
||||
import { Button, Modal, Splitter, Tooltip, Typography } from 'antd'
|
||||
import { Code, Eye, Maximize2, Minimize2, SaveIcon, SquareSplitHorizontal, X } from 'lucide-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -43,7 +43,7 @@ const HtmlArtifactsPopup: React.FC<HtmlArtifactsPopupProps> = ({ open, title, ht
|
||||
const renderHeader = () => (
|
||||
<ModalHeader onDoubleClick={() => setIsFullscreen(!isFullscreen)} className={classNames({ drag: isFullscreen })}>
|
||||
<HeaderLeft $isFullscreen={isFullscreen}>
|
||||
<TitleText>{title}</TitleText>
|
||||
<TitleText ellipsis={{ tooltip: true }}>{title}</TitleText>
|
||||
</HeaderLeft>
|
||||
|
||||
<HeaderCenter>
|
||||
@ -266,13 +266,13 @@ const HeaderRight = styled.div<{ $isFullscreen?: boolean }>`
|
||||
padding-right: ${({ $isFullscreen }) => ($isFullscreen ? (isWin ? '136px' : isLinux ? '120px' : '12px') : '12px')};
|
||||
`
|
||||
|
||||
const TitleText = styled.span`
|
||||
const TitleText = styled(Typography.Text)`
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-weight: bold;
|
||||
color: var(--color-text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 50%;
|
||||
`
|
||||
|
||||
const ViewControls = styled.div`
|
||||
|
||||
57
src/renderer/src/components/ErrorBoundary.tsx
Normal file
57
src/renderer/src/components/ErrorBoundary.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { formatErrorMessage } from '@renderer/utils/error'
|
||||
import { Alert, Button, Space } from 'antd'
|
||||
import { ComponentType, ReactNode } from 'react'
|
||||
import { ErrorBoundary, FallbackProps } from 'react-error-boundary'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const DefaultFallback: ComponentType<FallbackProps> = (props: FallbackProps): ReactNode => {
|
||||
const { t } = useTranslation()
|
||||
const { error } = props
|
||||
const debug = async () => {
|
||||
await window.api.devTools.toggle()
|
||||
}
|
||||
const reload = async () => {
|
||||
await window.api.reload()
|
||||
}
|
||||
return (
|
||||
<ErrorContainer>
|
||||
<Alert
|
||||
message={t('error.boundary.default.message')}
|
||||
showIcon
|
||||
description={formatErrorMessage(error)}
|
||||
type="error"
|
||||
action={
|
||||
<Space>
|
||||
<Button size="small" danger onClick={debug}>
|
||||
{t('error.boundary.default.devtools')}
|
||||
</Button>
|
||||
<Button size="small" danger onClick={reload}>
|
||||
{t('error.boundary.default.reload')}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
</ErrorContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const ErrorBoundaryCustomized = ({
|
||||
children,
|
||||
fallbackComponent
|
||||
}: {
|
||||
children: ReactNode
|
||||
fallbackComponent?: ComponentType<FallbackProps>
|
||||
}) => {
|
||||
return <ErrorBoundary FallbackComponent={fallbackComponent ?? DefaultFallback}>{children}</ErrorBoundary>
|
||||
}
|
||||
|
||||
const ErrorContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
`
|
||||
|
||||
export { ErrorBoundaryCustomized as ErrorBoundary }
|
||||
@ -166,7 +166,7 @@ export const SEARCH_SUMMARY_PROMPT = `
|
||||
</knowledge>
|
||||
\`
|
||||
|
||||
7. Follow up question: Based on knowledge, Fomula of Scaled Dot-Product Attention and Multi-Head Attention?
|
||||
7. Follow up question: Based on knowledge, Formula of Scaled Dot-Product Attention and Multi-Head Attention?
|
||||
Rephrased question: \`
|
||||
<websearch>
|
||||
<question>
|
||||
@ -279,7 +279,7 @@ export const SEARCH_SUMMARY_PROMPT_WEB_ONLY = `
|
||||
</websearch>
|
||||
\`
|
||||
|
||||
7. Follow up question: Based on knowledge, Fomula of Scaled Dot-Product Attention and Multi-Head Attention?
|
||||
7. Follow up question: Based on knowledge, Formula of Scaled Dot-Product Attention and Multi-Head Attention?
|
||||
Rephrased question: \`
|
||||
<websearch>
|
||||
<question>
|
||||
@ -374,7 +374,7 @@ export const SEARCH_SUMMARY_PROMPT_KNOWLEDGE_ONLY = `
|
||||
</knowledge>
|
||||
\`
|
||||
|
||||
7. Follow up question: Based on knowledge, Fomula of Scaled Dot-Product Attention and Multi-Head Attention?
|
||||
7. Follow up question: Based on knowledge, Formula of Scaled Dot-Product Attention and Multi-Head Attention?
|
||||
Rephrased question: \`
|
||||
<knowledge>
|
||||
<rewrite>
|
||||
|
||||
@ -66,7 +66,7 @@ db.version(6).stores({
|
||||
// --- NEW VERSION 7 ---
|
||||
db.version(7)
|
||||
.stores({
|
||||
// Re-declare all tables for the new version
|
||||
// Redeclare all tables for the new version
|
||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
||||
topics: '&id', // Correct index for topics
|
||||
settings: '&id, value',
|
||||
@ -79,7 +79,7 @@ db.version(7)
|
||||
|
||||
db.version(8)
|
||||
.stores({
|
||||
// Re-declare all tables for the new version
|
||||
// Redeclare all tables for the new version
|
||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
||||
topics: '&id', // Correct index for topics
|
||||
settings: '&id, value',
|
||||
@ -91,7 +91,7 @@ db.version(8)
|
||||
.upgrade((tx) => upgradeToV8(tx))
|
||||
|
||||
db.version(9).stores({
|
||||
// Re-declare all tables for the new version
|
||||
// Redeclare all tables for the new version
|
||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
||||
topics: '&id', // Correct index for topics
|
||||
settings: '&id, value',
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Backup file format error"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Open debug panel",
|
||||
"message": "It seems that something went wrong...",
|
||||
"reload": "Reload"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Returned an invalid data format"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "バックアップファイルの形式エラー"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "デバッグパネルを開く",
|
||||
"message": "何か問題が発生したようです...",
|
||||
"reload": "再読み込み"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "無効なデータ形式が返されました"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Ошибка формата файла резервной копии"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Открыть панель отладки",
|
||||
"message": "Похоже, возникла какая-то проблема...",
|
||||
"reload": "Перезагрузить"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Вернулся недопустимый формат данных"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "备份文件格式错误"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "打开调试面板",
|
||||
"message": "似乎出现了一些问题...",
|
||||
"reload": "重新加载"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "返回了无效的数据格式"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "備份檔案格式錯誤"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "打開除錯面板",
|
||||
"message": "似乎出現了一些問題...",
|
||||
"reload": "重新載入"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "返回了無效的資料格式"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Λάθος μορφή αρχείου που επιστρέφεται"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Άνοιγμα πίνακα αποσφαλμάτωσης",
|
||||
"message": "Φαίνεται ότι προέκυψε κάποιο πρόβλημα...",
|
||||
"reload": "Επαναφόρτωση"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Επέστρεψε μη έγκυρη μορφή δεδομένων"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Formato de archivo de copia de seguridad incorrecto"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Abrir el panel de depuración",
|
||||
"message": "Parece que ha surgido un problema...",
|
||||
"reload": "Recargar"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Devuelve un formato de datos no válido"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Le format du fichier de sauvegarde est incorrect"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Ouvrir le panneau de débogage",
|
||||
"message": "Il semble que quelques problèmes soient survenus...",
|
||||
"reload": "Recharger"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "a renvoyé un format de données invalide"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Formato do arquivo de backup está incorreto"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Abrir o painel de depuração",
|
||||
"message": "Parece que ocorreu um problema...",
|
||||
"reload": "Recarregar"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Devolveu um formato de dados inválido"
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useActiveTopic } from '@renderer/hooks/useTopic'
|
||||
@ -100,20 +101,24 @@ const HomePage: FC = () => {
|
||||
)}
|
||||
<ContentContainer id={isLeftNavbar ? 'content-container' : undefined}>
|
||||
{showAssistants && (
|
||||
<HomeTabs
|
||||
activeAssistant={activeAssistant}
|
||||
activeTopic={activeTopic}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
setActiveTopic={setActiveTopic}
|
||||
position="left"
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
<HomeTabs
|
||||
activeAssistant={activeAssistant}
|
||||
activeTopic={activeTopic}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
setActiveTopic={setActiveTopic}
|
||||
position="left"
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
<Chat
|
||||
assistant={activeAssistant}
|
||||
activeTopic={activeTopic}
|
||||
setActiveTopic={setActiveTopic}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
<Chat
|
||||
assistant={activeAssistant}
|
||||
activeTopic={activeTopic}
|
||||
setActiveTopic={setActiveTopic}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
|
||||
@ -83,7 +83,7 @@ export const getFileIcon = (type?: string) => {
|
||||
export const FileNameRender: FC<{ file: FileMetadata }> = ({ file }) => {
|
||||
const [visible, setVisible] = useState<boolean>(false)
|
||||
const isImage = (ext: string) => {
|
||||
return ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'].includes(ext)
|
||||
return ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'].includes(ext.toLocaleLowerCase())
|
||||
}
|
||||
|
||||
const fullName = FileManager.formatFileName(file)
|
||||
|
||||
@ -28,48 +28,62 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
const { providers } = useWebSearchProviders()
|
||||
const { updateAssistant } = useAssistant(assistant.id)
|
||||
|
||||
// 注意:assistant.enableWebSearch 有不同的语义
|
||||
/** 表示是否启用网络搜索 */
|
||||
const enableWebSearch = assistant?.webSearchProviderId || assistant.enableWebSearch
|
||||
|
||||
const WebSearchIcon = useCallback(
|
||||
({ pid, size = 18 }: { pid?: WebSearchProviderId; size?: number }) => {
|
||||
const iconColor = enableWebSearch ? 'var(--color-primary)' : 'var(--color-icon)'
|
||||
|
||||
({ pid, size = 18, color }: { pid?: WebSearchProviderId; size?: number; color?: string }) => {
|
||||
switch (pid) {
|
||||
case 'bocha':
|
||||
return <BochaLogo width={size} height={size} color={iconColor} />
|
||||
return <BochaLogo width={size} height={size} color={color} />
|
||||
case 'exa':
|
||||
// size微调,视觉上和其他图标平衡一些
|
||||
return <ExaLogo width={size - 2} height={size} color={iconColor} />
|
||||
return <ExaLogo width={size - 2} height={size} color={color} />
|
||||
case 'tavily':
|
||||
return <TavilyLogo width={size} height={size} color={iconColor} />
|
||||
return <TavilyLogo width={size} height={size} color={color} />
|
||||
case 'searxng':
|
||||
return <SearXNGLogo width={size} height={size} color={iconColor} />
|
||||
return <SearXNGLogo width={size} height={size} color={color} />
|
||||
case 'local-baidu':
|
||||
return <BaiduOutlined size={size} style={{ color: iconColor, fontSize: size }} />
|
||||
return <BaiduOutlined size={size} style={{ color, fontSize: size }} />
|
||||
case 'local-bing':
|
||||
return <BingLogo width={size} height={size} color={iconColor} />
|
||||
return <BingLogo width={size} height={size} color={color} />
|
||||
case 'local-google':
|
||||
return <GoogleOutlined size={size} style={{ color: iconColor, fontSize: size }} />
|
||||
return <GoogleOutlined size={size} style={{ color, fontSize: size }} />
|
||||
default:
|
||||
return <Globe size={size} style={{ color: iconColor, fontSize: size }} />
|
||||
return <Globe size={size} style={{ color, fontSize: size }} />
|
||||
}
|
||||
},
|
||||
[enableWebSearch]
|
||||
)
|
||||
|
||||
const updateSelectedWebSearchProvider = useCallback(
|
||||
const updateWebSearchProvider = useCallback(
|
||||
async (providerId?: WebSearchProvider['id']) => {
|
||||
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
||||
const currentWebSearchProviderId = assistant.webSearchProviderId
|
||||
const newWebSearchProviderId = currentWebSearchProviderId === providerId ? undefined : providerId
|
||||
startTransition(() => {
|
||||
updateAssistant({ ...assistant, webSearchProviderId: newWebSearchProviderId, enableWebSearch: false })
|
||||
updateAssistant({
|
||||
...assistant,
|
||||
webSearchProviderId: providerId,
|
||||
enableWebSearch: false
|
||||
})
|
||||
})
|
||||
},
|
||||
[assistant, updateAssistant]
|
||||
)
|
||||
|
||||
const updateSelectedWebSearchBuiltin = useCallback(async () => {
|
||||
const updateQuickPanelItem = useCallback(
|
||||
async (providerId?: WebSearchProvider['id']) => {
|
||||
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
||||
if (providerId === assistant.webSearchProviderId) {
|
||||
updateWebSearchProvider(undefined)
|
||||
} else {
|
||||
updateWebSearchProvider(providerId)
|
||||
}
|
||||
},
|
||||
[assistant.webSearchProviderId, updateWebSearchProvider]
|
||||
)
|
||||
|
||||
const updateToModelBuiltinWebSearch = useCallback(async () => {
|
||||
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
||||
startTransition(() => {
|
||||
updateAssistant({ ...assistant, webSearchProviderId: undefined, enableWebSearch: !assistant.enableWebSearch })
|
||||
@ -90,7 +104,7 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
icon: <WebSearchIcon size={13} pid={p.id} />,
|
||||
isSelected: p.id === assistant?.webSearchProviderId,
|
||||
disabled: !WebSearchService.isWebSearchEnabled(p.id),
|
||||
action: () => updateSelectedWebSearchProvider(p.id)
|
||||
action: () => updateQuickPanelItem(p.id)
|
||||
}))
|
||||
.filter((o) => !o.disabled)
|
||||
|
||||
@ -103,7 +117,7 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
icon: <Globe />,
|
||||
isSelected: assistant.enableWebSearch,
|
||||
disabled: !isWebSearchModelEnabled,
|
||||
action: () => updateSelectedWebSearchBuiltin()
|
||||
action: () => updateToModelBuiltinWebSearch()
|
||||
})
|
||||
}
|
||||
|
||||
@ -115,36 +129,18 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
assistant?.webSearchProviderId,
|
||||
providers,
|
||||
t,
|
||||
updateSelectedWebSearchBuiltin,
|
||||
updateSelectedWebSearchProvider
|
||||
updateQuickPanelItem,
|
||||
updateToModelBuiltinWebSearch
|
||||
])
|
||||
|
||||
const openQuickPanel = useCallback(() => {
|
||||
if (assistant.webSearchProviderId) {
|
||||
updateSelectedWebSearchProvider(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (assistant.enableWebSearch) {
|
||||
updateSelectedWebSearchBuiltin()
|
||||
return
|
||||
}
|
||||
|
||||
quickPanel.open({
|
||||
title: t('chat.input.web_search.label'),
|
||||
list: providerItems,
|
||||
symbol: '?',
|
||||
pageSize: 9
|
||||
})
|
||||
}, [
|
||||
assistant.webSearchProviderId,
|
||||
assistant.enableWebSearch,
|
||||
quickPanel,
|
||||
t,
|
||||
providerItems,
|
||||
updateSelectedWebSearchProvider,
|
||||
updateSelectedWebSearchBuiltin
|
||||
])
|
||||
}, [quickPanel, t, providerItems])
|
||||
|
||||
const handleOpenQuickPanel = useCallback(() => {
|
||||
if (quickPanel.isVisible && quickPanel.symbol === '?') {
|
||||
@ -154,18 +150,28 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
}
|
||||
}, [openQuickPanel, quickPanel])
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (enableWebSearch) {
|
||||
updateWebSearchProvider(undefined)
|
||||
} else {
|
||||
handleOpenQuickPanel()
|
||||
}
|
||||
}, [enableWebSearch, handleOpenQuickPanel, updateWebSearchProvider])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
openQuickPanel
|
||||
}))
|
||||
|
||||
const color = enableWebSearch ? 'var(--color-primary)' : 'var(--color-icon)'
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={enableWebSearch ? t('common.close') : t('chat.input.web_search.label')}
|
||||
mouseLeaveDelay={0}
|
||||
arrow>
|
||||
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
|
||||
<WebSearchIcon pid={assistant.webSearchProviderId} />
|
||||
<ToolbarButton type="text" onClick={onClick}>
|
||||
<WebSearchIcon color={color} pid={assistant.webSearchProviderId} />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// import { loggerService } from '@logger'
|
||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||
import { isBuiltinOcrProvider, OcrProvider } from '@renderer/types'
|
||||
import { getOcrProviderLogo } from '@renderer/utils/ocr'
|
||||
import { Avatar, Divider, Flex } from 'antd'
|
||||
@ -35,7 +36,7 @@ const OcrProviderSettings = ({ provider }: Props) => {
|
||||
</Flex>
|
||||
</SettingTitle>
|
||||
<Divider style={{ width: '100%', margin: '10px 0' }} />
|
||||
{getProviderSettings()}
|
||||
<ErrorBoundary>{getProviderSettings()}</ErrorBoundary>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { PictureOutlined } from '@ant-design/icons'
|
||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { OcrProvider } from '@renderer/types'
|
||||
@ -26,7 +27,7 @@ const OcrSettings: FC = () => {
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<ErrorBoundary>
|
||||
<SettingGroup theme={themeMode}>
|
||||
<SettingTitle>{t('settings.tool.ocr.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
@ -35,7 +36,7 @@ const OcrSettings: FC = () => {
|
||||
<SettingGroup theme={themeMode}>
|
||||
<OcrProviderSettings provider={provider} />
|
||||
</SettingGroup>
|
||||
</>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
export default OcrSettings
|
||||
|
||||
@ -13,7 +13,6 @@ export const OcrTesseractSettings = () => {
|
||||
const { t } = useTranslation()
|
||||
const { provider } = useOcrProvider(BuiltinOcrProviderIds.tesseract)
|
||||
|
||||
// TODO: use error boundary
|
||||
if (!isOcrTesseractProvider(provider)) {
|
||||
throw new Error('Not tesseract provider.')
|
||||
}
|
||||
|
||||
@ -477,8 +477,9 @@ export async function fetchChatCompletion({
|
||||
assistant.settings?.reasoning_effort !== undefined) ||
|
||||
(isReasoningModel(model) && (!isSupportedThinkingTokenModel(model) || !isSupportedReasoningEffortModel(model)))
|
||||
|
||||
// NOTE:assistant.enableWebSearch 的语义是是否启用模型内置搜索功能
|
||||
const enableWebSearch =
|
||||
(assistant.enableWebSearch && isWebSearchModel(model)) ||
|
||||
(assistant.webSearchProviderId && isWebSearchModel(model)) ||
|
||||
isOpenRouterBuiltInWebSearchModel(model) ||
|
||||
model.id.includes('sonar') ||
|
||||
false
|
||||
|
||||
@ -185,7 +185,7 @@ export function mcpToolsToAnthropicTools(mcpTools: MCPTool[]): Array<ToolUnion>
|
||||
const t: ToolUnion = {
|
||||
name: tool.id,
|
||||
description: tool.description,
|
||||
// @ts-ignore ignore type as it it unknow
|
||||
// @ts-ignore ignore type as it it unknown
|
||||
input_schema: tool.inputSchema
|
||||
}
|
||||
return t
|
||||
|
||||
@ -53,7 +53,7 @@ export const detectLanguage = async (inputText: string): Promise<TranslateLangua
|
||||
}
|
||||
|
||||
const detectLanguageByLLM = async (inputText: string): Promise<TranslateLanguageCode> => {
|
||||
logger.info('Detect langugage by llm')
|
||||
logger.info('Detect language by llm')
|
||||
let detectedLang = ''
|
||||
await fetchLanguageDetection({
|
||||
text: sliceByTokens(inputText, 0, 100),
|
||||
@ -65,7 +65,7 @@ const detectLanguageByLLM = async (inputText: string): Promise<TranslateLanguage
|
||||
}
|
||||
|
||||
const detectLanguageByFranc = (inputText: string): TranslateLanguageCode => {
|
||||
logger.info('Detect langugage by franc')
|
||||
logger.info('Detect language by franc')
|
||||
const iso3 = franc(inputText)
|
||||
|
||||
const isoMap: Record<string, TranslateLanguage> = {
|
||||
|
||||
@ -21,7 +21,7 @@ import { getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||
import { defaultLanguage } from '@shared/config/constant'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { Divider } from 'antd'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { cloneDeep, isEmpty } from 'lodash'
|
||||
import { last } from 'lodash'
|
||||
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -256,9 +256,19 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
||||
setIsFirstMessage(false)
|
||||
setUserInputText('')
|
||||
|
||||
const newAssistant = cloneDeep(currentAssistant)
|
||||
if (!newAssistant.settings) {
|
||||
newAssistant.settings = {}
|
||||
}
|
||||
newAssistant.settings.streamOutput = true
|
||||
// 显式关闭这些功能
|
||||
// newAssistant.webSearchProviderId = undefined
|
||||
newAssistant.mcpServers = undefined
|
||||
// newAssistant.knowledge_bases = undefined
|
||||
|
||||
await fetchChatCompletion({
|
||||
messages: messagesForContext,
|
||||
assistant: { ...currentAssistant, settings: { streamOutput: true } },
|
||||
assistant: newAssistant,
|
||||
onChunkReceived: (chunk: Chunk) => {
|
||||
switch (chunk.type) {
|
||||
case ChunkType.THINKING_START:
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { loggerService } from '@logger'
|
||||
import CopyButton from '@renderer/components/CopyButton'
|
||||
import { useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
@ -21,6 +22,7 @@ import styled from 'styled-components'
|
||||
import { processMessages } from './ActionUtils'
|
||||
import WindowFooter from './WindowFooter'
|
||||
|
||||
const logger = loggerService.withContext('ActionGeneral')
|
||||
interface Props {
|
||||
action: ActionItem
|
||||
scrollToBottom?: () => void
|
||||
@ -112,6 +114,7 @@ const ActionGeneral: FC<Props> = React.memo(({ action, scrollToBottom }) => {
|
||||
}
|
||||
|
||||
if (!assistantRef.current || !topicRef.current) return
|
||||
logger.debug('Before peocess message', { assistant: assistantRef.current })
|
||||
processMessages(
|
||||
assistantRef.current,
|
||||
topicRef.current,
|
||||
|
||||
@ -10,6 +10,7 @@ import { Chunk, ChunkType } from '@renderer/types/chunk'
|
||||
import { AssistantMessageStatus, MessageBlockStatus } from '@renderer/types/newMessage'
|
||||
import { formatErrorMessage, isAbortError } from '@renderer/utils/error'
|
||||
import { createErrorBlock, createMainTextBlock, createThinkingBlock } from '@renderer/utils/messageUtils/create'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
const logger = loggerService.withContext('ActionUtils')
|
||||
|
||||
@ -53,9 +54,19 @@ export const processMessages = async (
|
||||
|
||||
let finished = false
|
||||
|
||||
const newAssistant = cloneDeep(assistant)
|
||||
if (!newAssistant.settings) {
|
||||
newAssistant.settings = {}
|
||||
}
|
||||
newAssistant.settings.streamOutput = true
|
||||
// 显式关闭这些功能
|
||||
newAssistant.webSearchProviderId = undefined
|
||||
newAssistant.mcpServers = undefined
|
||||
// newAssistant.knowledge_bases = undefined
|
||||
|
||||
await fetchChatCompletion({
|
||||
messages: [userMessage],
|
||||
assistant: { ...assistant, settings: { streamOutput: true } },
|
||||
assistant: newAssistant,
|
||||
onChunkReceived: (chunk: Chunk) => {
|
||||
if (finished) {
|
||||
return
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@ -8538,6 +8538,7 @@ __metadata:
|
||||
proxy-agent: "npm:^6.5.0"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
react-error-boundary: "npm:^6.0.0"
|
||||
react-hotkeys-hook: "npm:^4.6.1"
|
||||
react-i18next: "npm:^14.1.2"
|
||||
react-infinite-scroll-component: "npm:^6.1.0"
|
||||
@ -19057,6 +19058,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-error-boundary@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "react-error-boundary@npm:6.0.0"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.12.5"
|
||||
peerDependencies:
|
||||
react: ">=16.13.1"
|
||||
checksum: 10c0/1914d600dee95a14f14af4afe9867b0d35c26c4f7826d23208800ba2a99728659029aad60a6ef95e13430b4d79c2c4c9b3585f50bf508450478760d2e4e732d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-hotkeys-hook@npm:^4.6.1":
|
||||
version: 4.6.2
|
||||
resolution: "react-hotkeys-hook@npm:4.6.2"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user