Merge remote-tracking branch 'upstream' into feat/ocr

This commit is contained in:
icarus 2025-08-24 18:54:35 +08:00
commit 0569d146f4
29 changed files with 275 additions and 94 deletions

View File

@ -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",

View File

@ -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}`
}
})

View File

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

View File

@ -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

View File

@ -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>
)
}, [])

View File

@ -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`

View File

@ -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`

View 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 }

View File

@ -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>

View File

@ -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',

View File

@ -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"

View File

@ -808,6 +808,13 @@
"backup": {
"file_format": "バックアップファイルの形式エラー"
},
"boundary": {
"default": {
"devtools": "デバッグパネルを開く",
"message": "何か問題が発生したようです...",
"reload": "再読み込み"
}
},
"chat": {
"chunk": {
"non_json": "無効なデータ形式が返されました"

View File

@ -808,6 +808,13 @@
"backup": {
"file_format": "Ошибка формата файла резервной копии"
},
"boundary": {
"default": {
"devtools": "Открыть панель отладки",
"message": "Похоже, возникла какая-то проблема...",
"reload": "Перезагрузить"
}
},
"chat": {
"chunk": {
"non_json": "Вернулся недопустимый формат данных"

View File

@ -808,6 +808,13 @@
"backup": {
"file_format": "备份文件格式错误"
},
"boundary": {
"default": {
"devtools": "打开调试面板",
"message": "似乎出现了一些问题...",
"reload": "重新加载"
}
},
"chat": {
"chunk": {
"non_json": "返回了无效的数据格式"

View File

@ -808,6 +808,13 @@
"backup": {
"file_format": "備份檔案格式錯誤"
},
"boundary": {
"default": {
"devtools": "打開除錯面板",
"message": "似乎出現了一些問題...",
"reload": "重新載入"
}
},
"chat": {
"chunk": {
"non_json": "返回了無效的資料格式"

View File

@ -808,6 +808,13 @@
"backup": {
"file_format": "Λάθος μορφή αρχείου που επιστρέφεται"
},
"boundary": {
"default": {
"devtools": "Άνοιγμα πίνακα αποσφαλμάτωσης",
"message": "Φαίνεται ότι προέκυψε κάποιο πρόβλημα...",
"reload": "Επαναφόρτωση"
}
},
"chat": {
"chunk": {
"non_json": "Επέστρεψε μη έγκυρη μορφή δεδομένων"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

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

View File

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

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

View File

@ -477,8 +477,9 @@ export async function fetchChatCompletion({
assistant.settings?.reasoning_effort !== undefined) ||
(isReasoningModel(model) && (!isSupportedThinkingTokenModel(model) || !isSupportedReasoningEffortModel(model)))
// NOTEassistant.enableWebSearch 的语义是是否启用模型内置搜索功能
const enableWebSearch =
(assistant.enableWebSearch && isWebSearchModel(model)) ||
(assistant.webSearchProviderId && isWebSearchModel(model)) ||
isOpenRouterBuiltInWebSearchModel(model) ||
model.id.includes('sonar') ||
false

View File

@ -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

View File

@ -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> = {

View File

@ -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:

View File

@ -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,

View File

@ -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

View File

@ -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"