From 089477eb1e4cd247ad041661d571723415fa044c Mon Sep 17 00:00:00 2001 From: one Date: Tue, 2 Sep 2025 21:52:14 +0800 Subject: [PATCH 1/5] refactor(CodeViewer): improve props, aligned to CodeEditor (#9786) * refactor(CodeViewer): improve props, aligned to CodeEditor * refactor: simplify internal variables * refactor: remove default lineNumbers * fix: shiki theme container style * revert: use ReactMarkdown for prompt editing --- .../src/components/CodeBlockView/view.tsx | 7 +- .../src/components/CodeEditor/index.tsx | 14 +-- src/renderer/src/components/CodeViewer.tsx | 89 ++++++++++++++----- .../AssistantPromptSettings.tsx | 12 ++- 4 files changed, 89 insertions(+), 33 deletions(-) diff --git a/src/renderer/src/components/CodeBlockView/view.tsx b/src/renderer/src/components/CodeBlockView/view.tsx index 174327ae68..46f6cbce86 100644 --- a/src/renderer/src/components/CodeBlockView/view.tsx +++ b/src/renderer/src/components/CodeBlockView/view.tsx @@ -257,12 +257,13 @@ export const CodeBlockView: React.FC = memo(({ children, language, onSave ) : ( - {children} - + maxHeight={`${MAX_COLLAPSED_CODE_HEIGHT}px`} + /> ), [children, codeEditor.enabled, handleHeightChange, language, onSave, shouldExpand, shouldWrap] ) diff --git a/src/renderer/src/components/CodeEditor/index.tsx b/src/renderer/src/components/CodeEditor/index.tsx index 7c541cbdb8..4304ec324e 100644 --- a/src/renderer/src/components/CodeEditor/index.tsx +++ b/src/renderer/src/components/CodeEditor/index.tsx @@ -48,8 +48,6 @@ export interface CodeEditorProps { maxHeight?: string /** Minimum editor height. */ minHeight?: string - /** Font size that overrides the app setting. */ - fontSize?: string /** Editor options that extend BasicSetupOptions. */ options?: { /** @@ -70,6 +68,8 @@ export interface CodeEditorProps { } & BasicSetupOptions /** Additional extensions for CodeMirror. */ extensions?: Extension[] + /** Font size that overrides the app setting. */ + fontSize?: number /** Style overrides for the editor, passed directly to CodeMirror's style property. */ style?: React.CSSProperties /** CSS class name appended to the default `code-editor` class. */ @@ -108,9 +108,9 @@ const CodeEditor = ({ height, maxHeight, minHeight, - fontSize, options, extensions, + fontSize: customFontSize, style, className, editable = true, @@ -121,7 +121,7 @@ const CodeEditor = ({ const enableKeymap = useMemo(() => options?.keymap ?? codeEditor.keymap, [options?.keymap, codeEditor.keymap]) // 合并 codeEditor 和 options 的 basicSetup,options 优先 - const customBasicSetup = useMemo(() => { + const basicSetup = useMemo(() => { return { lineNumbers: _lineNumbers, ...(codeEditor as BasicSetupOptions), @@ -129,7 +129,7 @@ const CodeEditor = ({ } }, [codeEditor, _lineNumbers, options]) - const customFontSize = useMemo(() => fontSize ?? `${_fontSize - 1}px`, [fontSize, _fontSize]) + const fontSize = useMemo(() => customFontSize ?? _fontSize - 1, [customFontSize, _fontSize]) const { activeCmTheme } = useCodeStyle() const initialContent = useRef(options?.stream ? (value ?? '').trimEnd() : (value ?? '')) @@ -214,10 +214,10 @@ const CodeEditor = ({ foldKeymap: enableKeymap, completionKeymap: enableKeymap, lintKeymap: enableKeymap, - ...customBasicSetup // override basicSetup + ...basicSetup // override basicSetup }} style={{ - fontSize: customFontSize, + fontSize, marginTop: 0, borderRadius: 'inherit', ...style diff --git a/src/renderer/src/components/CodeViewer.tsx b/src/renderer/src/components/CodeViewer.tsx index 440aed9c7c..e8080d9518 100644 --- a/src/renderer/src/components/CodeViewer.tsx +++ b/src/renderer/src/components/CodeViewer.tsx @@ -1,4 +1,3 @@ -import { MAX_COLLAPSED_CODE_HEIGHT } from '@renderer/config/constant' import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import { useCodeHighlight } from '@renderer/hooks/useCodeHighlight' import { useSettings } from '@renderer/hooks/useSettings' @@ -11,13 +10,49 @@ import { ThemedToken } from 'shiki/core' import styled from 'styled-components' interface CodeViewerProps { + /** Code string value. */ + value: string + /** + * Code language string. + * - Case-insensitive. + * - Supports common names: javascript, json, python, etc. + * - Supports shiki aliases: c#/csharp, objective-c++/obj-c++/objc++, etc. + */ language: string - children: string - expanded?: boolean - wrapped?: boolean + /** Fired when the editor height changes. */ onHeightChange?: (scrollHeight: number) => void - className?: string + /** + * Height of the scroll container. + * Only works when expanded is false. + */ height?: string | number + /** + * Maximum height of the scroll container. + * Only works when expanded is false. + */ + maxHeight?: string | number + /** Viewer options. */ + options?: { + /** + * Whether to show line numbers. + */ + lineNumbers?: boolean + } + /** Font size that overrides the app setting. */ + fontSize?: number + /** CSS class name appended to the default `code-viewer` class. */ + className?: string + /** + * Whether the editor is expanded. + * If true, the height and maxHeight props are ignored. + * @default true + */ + expanded?: boolean + /** + * Whether the code lines are wrapped. + * @default true + */ + wrapped?: boolean } /** @@ -26,19 +61,33 @@ interface CodeViewerProps { * - 使用虚拟滚动和按需高亮,改善页面内有大量长代码块时的响应 * - 并发安全 */ -const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, className, height }: CodeViewerProps) => { - const { codeShowLineNumbers, fontSize } = useSettings() +const CodeViewer = ({ + value, + language, + height, + maxHeight, + onHeightChange, + options, + fontSize: customFontSize, + className, + expanded = true, + wrapped = true +}: CodeViewerProps) => { + const { codeShowLineNumbers: _lineNumbers, fontSize: _fontSize } = useSettings() const { getShikiPreProperties, isShikiThemeDark } = useCodeStyle() const shikiThemeRef = useRef(null) const scrollerRef = useRef(null) const callerId = useRef(`${Date.now()}-${uuid()}`).current - const rawLines = useMemo(() => (typeof children === 'string' ? children.trimEnd().split('\n') : []), [children]) + const fontSize = useMemo(() => customFontSize ?? _fontSize - 1, [customFontSize, _fontSize]) + const lineNumbers = useMemo(() => options?.lineNumbers ?? _lineNumbers, [options?.lineNumbers, _lineNumbers]) + + const rawLines = useMemo(() => (typeof value === 'string' ? value.trimEnd().split('\n') : []), [value]) // 计算行号数字位数 const gutterDigits = useMemo( - () => (codeShowLineNumbers ? Math.max(rawLines.length.toString().length, 1) : 0), - [codeShowLineNumbers, rawLines.length] + () => (lineNumbers ? Math.max(rawLines.length.toString().length, 1) : 0), + [lineNumbers, rawLines.length] ) // 设置 pre 标签属性 @@ -68,7 +117,7 @@ const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, cla const getScrollElement = useCallback(() => scrollerRef.current, []) const getItemKey = useCallback((index: number) => `${callerId}-${index}`, [callerId]) // `line-height: 1.6` 为全局样式,但是为了避免测量误差在这里取整 - const estimateSize = useCallback(() => Math.round((fontSize - 1) * 1.6), [fontSize]) + const estimateSize = useCallback(() => Math.round(fontSize * 1.6), [fontSize]) // 创建 virtualizer 实例 const virtualizer = useVirtualizer({ @@ -105,20 +154,19 @@ const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, cla }, [rawLines.length, onHeightChange]) return ( -
+
@@ -142,7 +190,7 @@ const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, cla
@@ -226,9 +274,8 @@ VirtualizedRow.displayName = 'VirtualizedRow' const ScrollContainer = styled.div<{ $wrap?: boolean - $expanded?: boolean + $expand?: boolean $lineHeight?: number - $height?: string | number }>` display: block; overflow-x: auto; @@ -244,7 +291,7 @@ const ScrollContainer = styled.div<{ line-height: ${(props) => props.$lineHeight}px; /* contain 优化 wrap 时滚动性能,will-change 优化 unwrap 时滚动性能 */ contain: ${(props) => (props.$wrap ? 'content' : 'none')}; - will-change: ${(props) => (!props.$wrap && !props.$expanded ? 'transform' : 'auto')}; + will-change: ${(props) => (!props.$wrap && !props.$expand ? 'transform' : 'auto')}; .line-number { width: var(--gutter-width, 1.2ch); diff --git a/src/renderer/src/pages/settings/AssistantSettings/AssistantPromptSettings.tsx b/src/renderer/src/pages/settings/AssistantSettings/AssistantPromptSettings.tsx index 9f27db6afb..a2ce2657b9 100644 --- a/src/renderer/src/pages/settings/AssistantSettings/AssistantPromptSettings.tsx +++ b/src/renderer/src/pages/settings/AssistantSettings/AssistantPromptSettings.tsx @@ -2,7 +2,6 @@ import 'emoji-picker-element' import { CloseCircleFilled } from '@ant-design/icons' import CodeEditor from '@renderer/components/CodeEditor' -import CodeViewer from '@renderer/components/CodeViewer' import EmojiPicker from '@renderer/components/EmojiPicker' import { Box, HSpaceBetweenStack, HStack } from '@renderer/components/Layout' import { RichEditorRef } from '@renderer/components/RichEditor/types' @@ -14,6 +13,7 @@ import { Button, Input, Popover } from 'antd' import { Edit, HelpCircle, Save } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import ReactMarkdown from 'react-markdown' import styled from 'styled-components' import { SettingDivider } from '..' @@ -122,7 +122,9 @@ const AssistantPromptSettings: React.FC = ({ assistant, updateAssistant } {showPreview ? ( - + + {processedPrompt || prompt} + ) : ( Date: Tue, 2 Sep 2025 23:28:29 +0800 Subject: [PATCH 2/5] fix: draggable list id type (#9809) * refactor(dnd): rename idKey to itemKey for clarity * refactor: key and id type for draggable lists * chore: update yarn lock * fix: type error * refactor: improve getId fallbacks --- .../__tests__/DraggableList.test.tsx | 3 ++- .../__tests__/useDraggableReorder.test.ts | 10 ++++---- .../src/components/DraggableList/list.tsx | 23 +++++++++++++++---- .../DraggableList/useDraggableReorder.ts | 14 ++++++++--- .../components/DraggableList/virtual-list.tsx | 2 +- .../src/components/dnd/useDndReorder.ts | 9 +++++--- .../settings/MCPSettings/McpServersList.tsx | 2 +- .../ProviderSettings/ProviderList.tsx | 2 +- 8 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/renderer/src/components/DraggableList/__tests__/DraggableList.test.tsx b/src/renderer/src/components/DraggableList/__tests__/DraggableList.test.tsx index 2d61583787..87765a504b 100644 --- a/src/renderer/src/components/DraggableList/__tests__/DraggableList.test.tsx +++ b/src/renderer/src/components/DraggableList/__tests__/DraggableList.test.tsx @@ -71,8 +71,9 @@ describe('DraggableList', () => { }) it('should render nothing when list is empty', () => { + const emptyList: Array<{ id: string; name: string }> = [] render( - {}}> + {}}> {(item) =>
{item.name}
}
) diff --git a/src/renderer/src/components/DraggableList/__tests__/useDraggableReorder.test.ts b/src/renderer/src/components/DraggableList/__tests__/useDraggableReorder.test.ts index f2d2fe837f..a9ffd3d889 100644 --- a/src/renderer/src/components/DraggableList/__tests__/useDraggableReorder.test.ts +++ b/src/renderer/src/components/DraggableList/__tests__/useDraggableReorder.test.ts @@ -33,7 +33,7 @@ describe('useDraggableReorder', () => { originalList: mockOriginalList, filteredList: mockOriginalList, // 列表未过滤 onUpdate, - idKey: 'id' + itemKey: 'id' }) ) @@ -61,7 +61,7 @@ describe('useDraggableReorder', () => { originalList: mockOriginalList, filteredList, onUpdate, - idKey: 'id' + itemKey: 'id' }) ) @@ -89,7 +89,7 @@ describe('useDraggableReorder', () => { originalList: mockOriginalList, filteredList: mockOriginalList, onUpdate, - idKey: 'id' + itemKey: 'id' }) ) @@ -110,7 +110,7 @@ describe('useDraggableReorder', () => { originalList: mockOriginalList, filteredList: mockOriginalList, onUpdate, - idKey: 'id' + itemKey: 'id' }) ) @@ -136,7 +136,7 @@ describe('useDraggableReorder', () => { originalList: mockOriginalList, filteredList, onUpdate, - idKey: 'id' + itemKey: 'id' }) ) diff --git a/src/renderer/src/components/DraggableList/list.tsx b/src/renderer/src/components/DraggableList/list.tsx index b0e87bd2d2..fbb5f29762 100644 --- a/src/renderer/src/components/DraggableList/list.tsx +++ b/src/renderer/src/components/DraggableList/list.tsx @@ -9,7 +9,7 @@ import { ResponderProvided } from '@hello-pangea/dnd' import { droppableReorder } from '@renderer/utils' -import { FC, HTMLAttributes } from 'react' +import { HTMLAttributes, Key, useCallback } from 'react' interface Props { list: T[] @@ -17,23 +17,25 @@ interface Props { listStyle?: React.CSSProperties listProps?: HTMLAttributes children: (item: T, index: number) => React.ReactNode + itemKey?: keyof T | ((item: T) => Key) onUpdate: (list: T[]) => void onDragStart?: OnDragStartResponder onDragEnd?: OnDragEndResponder droppableProps?: Partial } -const DraggableList: FC> = ({ +function DraggableList({ children, list, style, listStyle, listProps, + itemKey, droppableProps, onDragStart, onUpdate, onDragEnd -}) => { +}: Props) { const _onDragEnd = (result: DropResult, provided: ResponderProvided) => { onDragEnd?.(result, provided) if (result.destination) { @@ -46,6 +48,17 @@ const DraggableList: FC> = ({ } } + const getId = useCallback( + (item: T) => { + if (typeof itemKey === 'function') return itemKey(item) + if (itemKey) return item[itemKey] as Key + if (typeof item === 'string') return item as Key + if (item && typeof item === 'object' && 'id' in item) return item.id as Key + return undefined + }, + [itemKey] + ) + return ( @@ -53,9 +66,9 @@ const DraggableList: FC> = ({
{list.map((item, index) => { - const id = item.id || item + const draggableId = String(getId(item) ?? index) return ( - + {(provided) => (
{ /** 用于更新原始列表状态的函数 */ onUpdate: (newList: T[]) => void /** 用于从列表项中获取唯一ID的属性名或函数 */ - idKey: keyof T | ((item: T) => Key) + itemKey: keyof T | ((item: T) => Key) } /** @@ -19,8 +19,16 @@ interface UseDraggableReorderParams { * @param params - { originalList, filteredList, onUpdate, idKey } * @returns 返回可以直接传递给 DraggableVirtualList 的 props: { onDragEnd, itemKey } */ -export function useDraggableReorder({ originalList, filteredList, onUpdate, idKey }: UseDraggableReorderParams) { - const getId = useCallback((item: T) => (typeof idKey === 'function' ? idKey(item) : (item[idKey] as Key)), [idKey]) +export function useDraggableReorder({ + originalList, + filteredList, + onUpdate, + itemKey +}: UseDraggableReorderParams) { + const getId = useCallback( + (item: T) => (typeof itemKey === 'function' ? itemKey(item) : (item[itemKey] as Key)), + [itemKey] + ) // 创建从 item ID 到其在 *原始列表* 中索引的映射 const itemIndexMap = useMemo(() => { diff --git a/src/renderer/src/components/DraggableList/virtual-list.tsx b/src/renderer/src/components/DraggableList/virtual-list.tsx index e915eec1fe..b2efe7c247 100644 --- a/src/renderer/src/components/DraggableList/virtual-list.tsx +++ b/src/renderer/src/components/DraggableList/virtual-list.tsx @@ -208,7 +208,7 @@ const VirtualRow = memo( const draggableId = String(virtualItem.key) return ( diff --git a/src/renderer/src/components/dnd/useDndReorder.ts b/src/renderer/src/components/dnd/useDndReorder.ts index 1b91507cb5..60beaf925a 100644 --- a/src/renderer/src/components/dnd/useDndReorder.ts +++ b/src/renderer/src/components/dnd/useDndReorder.ts @@ -8,7 +8,7 @@ interface UseDndReorderParams { /** 用于更新原始列表状态的函数 */ onUpdate: (newList: T[]) => void /** 用于从列表项中获取唯一ID的属性名或函数 */ - idKey: keyof T | ((item: T) => Key) + itemKey: keyof T | ((item: T) => Key) } /** @@ -18,8 +18,11 @@ interface UseDndReorderParams { * @param params - { originalList, filteredList, onUpdate, idKey } * @returns 返回可以直接传递给 Sortable 的 onSortEnd 回调 */ -export function useDndReorder({ originalList, filteredList, onUpdate, idKey }: UseDndReorderParams) { - const getId = useCallback((item: T) => (typeof idKey === 'function' ? idKey(item) : (item[idKey] as Key)), [idKey]) +export function useDndReorder({ originalList, filteredList, onUpdate, itemKey }: UseDndReorderParams) { + const getId = useCallback( + (item: T) => (typeof itemKey === 'function' ? itemKey(item) : (item[itemKey] as Key)), + [itemKey] + ) // 创建从 item ID 到其在 *原始列表* 中索引的映射 const itemIndexMap = useMemo(() => { diff --git a/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx b/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx index f9f98476d9..bbd108a26b 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx @@ -55,7 +55,7 @@ const McpServersList: FC = () => { originalList: mcpServers, filteredList: filteredMcpServers, onUpdate: updateMcpServers, - idKey: 'id' + itemKey: 'id' }) const scrollRef = useRef(null) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx index a5f2b0139e..f04f62def6 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx @@ -321,7 +321,7 @@ const ProviderList: FC = () => { originalList: providers, filteredList: filteredProviders, onUpdate: updateProviders, - idKey: 'id' + itemKey: 'id' }) const handleDragStart = useCallback(() => { From 362658339a0856af3d4f012125913ee519af4eea Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Tue, 2 Sep 2025 23:34:08 +0800 Subject: [PATCH 3/5] feat: integrate file selection and upload functionality in KnowledgeFiles component (#9815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: integrate file selection and upload functionality in KnowledgeFiles component - Added useFiles hook to manage file selection. - Updated handleAddFile to utilize the new file selection logic, allowing multiple file uploads. - Improved user experience by handling file uploads asynchronously and logging the results. * feat: enhance file upload interaction in KnowledgeFiles component - Wrapped Dragger component in a div to allow for custom click handling. - Prevented default click behavior to improve user experience when adding files. - Maintained existing file upload functionality while enhancing the UI interaction. * refactor(KnowledgeFiles): 提取文件处理逻辑到独立函数 将重复的文件上传和处理逻辑提取到独立的processFiles函数中,提高代码复用性和可维护性 --------- Co-authored-by: icarus --- .../pages/knowledge/items/KnowledgeFiles.tsx | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/src/renderer/src/pages/knowledge/items/KnowledgeFiles.tsx b/src/renderer/src/pages/knowledge/items/KnowledgeFiles.tsx index 9dfcd955b6..0e2e26c9f5 100644 --- a/src/renderer/src/pages/knowledge/items/KnowledgeFiles.tsx +++ b/src/renderer/src/pages/knowledge/items/KnowledgeFiles.tsx @@ -1,5 +1,6 @@ import { loggerService } from '@logger' import Ellipsis from '@renderer/components/Ellipsis' +import { useFiles } from '@renderer/hooks/useFiles' import { useKnowledge } from '@renderer/hooks/useKnowledge' import FileItem from '@renderer/pages/files/FileItem' import StatusIcon from '@renderer/pages/knowledge/components/StatusIcon' @@ -48,6 +49,7 @@ const getDisplayTime = (item: KnowledgeItem) => { const KnowledgeFiles: FC = ({ selectedBase, progressMap, preprocessMap }) => { const { t } = useTranslation() const [windowHeight, setWindowHeight] = useState(window.innerHeight) + const { onSelectFile, selecting } = useFiles({ extensions: fileTypes }) const { base, fileItems, addFiles, refreshItem, removeItem, getProcessingStatus } = useKnowledge( selectedBase.id || '' @@ -71,19 +73,12 @@ const KnowledgeFiles: FC = ({ selectedBase, progressMap, return null } - const handleAddFile = () => { - if (disabled) { + const handleAddFile = async () => { + if (disabled || selecting) { return } - const input = document.createElement('input') - input.type = 'file' - input.multiple = true - input.accept = fileTypes.join(',') - input.onchange = (e) => { - const files = (e.target as HTMLInputElement).files - files && handleDrop(Array.from(files)) - } - input.click() + const selectedFiles = await onSelectFile({ multipleSelections: true }) + processFiles(selectedFiles) } const handleDrop = async (files: File[]) => { @@ -118,8 +113,14 @@ const KnowledgeFiles: FC = ({ selectedBase, progressMap, } }) .filter(({ ext }) => fileTypes.includes(ext)) - const uploadedFiles = await FileManager.uploadFiles(_files) - logger.debug('uploadedFiles', uploadedFiles) + processFiles(_files) + } + } + + const processFiles = async (files: FileMetadata[]) => { + logger.debug('processFiles', files) + if (files.length > 0) { + const uploadedFiles = await FileManager.uploadFiles(files) addFiles(uploadedFiles) } } @@ -150,16 +151,23 @@ const KnowledgeFiles: FC = ({ selectedBase, progressMap, - handleDrop([file as File])} - multiple={true} - accept={fileTypes.join(',')}> -

{t('knowledge.drag_file')}

-

- {t('knowledge.file_hint', { file_types: 'TXT, MD, HTML, PDF, DOCX, PPTX, XLSX, EPUB...' })} -

-
+
{ + e.stopPropagation() + handleAddFile() + }}> + handleDrop([file as File])} + multiple={true} + accept={fileTypes.join(',')} + openFileDialogOnClick={false}> +

{t('knowledge.drag_file')}

+

+ {t('knowledge.file_hint', { file_types: 'TXT, MD, HTML, PDF, DOCX, PPTX, XLSX, EPUB...' })} +

+
+
{fileItems.length === 0 ? ( ) : ( From 76ac1bd8f79b606acd46894d1a681ac489d0359d Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Wed, 3 Sep 2025 11:43:42 +0800 Subject: [PATCH 4/5] fix: enhance provider selection logic in AssistantService - Updated getProviderByModel function to improve provider selection. - Added fallback logic to return a default or cherryin provider if the specified model provider is not found. - Ensured that the first provider is returned as a last resort, enhancing robustness in provider retrieval. --- src/renderer/src/services/AssistantService.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/services/AssistantService.ts b/src/renderer/src/services/AssistantService.ts index fe4c040d0d..567c59991c 100644 --- a/src/renderer/src/services/AssistantService.ts +++ b/src/renderer/src/services/AssistantService.ts @@ -134,8 +134,15 @@ export function getAssistantProvider(assistant: Assistant): Provider { export function getProviderByModel(model?: Model): Provider { const providers = store.getState().llm.providers - const providerId = model ? model.provider : getDefaultProvider().id - return providers.find((p) => p.id === providerId) as Provider + const provider = providers.find((p) => p.id === model?.provider) + + if (!provider) { + const defaultProvider = providers.find((p) => p.id === getDefaultModel()?.provider) + const cherryinProvider = providers.find((p) => p.id === 'cherryin') + return defaultProvider || cherryinProvider || providers[0] + } + + return provider } export function getProviderByModelId(modelId?: string) { From fdee510c8c3ad7b8cf04acc1405954a550074218 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Wed, 3 Sep 2025 11:55:56 +0800 Subject: [PATCH 5/5] feat: add 'invalid_model' translation key across multiple languages - Introduced a new translation key 'invalid_model' in English, Japanese, Russian, Chinese (Simplified and Traditional), Greek, Spanish, French, and Portuguese. - Updated the SelectModelButton component to display an error tag when no valid provider is found, enhancing user feedback. --- src/renderer/src/components/Preview/MermaidPreview.tsx | 1 + src/renderer/src/i18n/locales/en-us.json | 1 + src/renderer/src/i18n/locales/ja-jp.json | 1 + src/renderer/src/i18n/locales/ru-ru.json | 1 + src/renderer/src/i18n/locales/zh-cn.json | 1 + src/renderer/src/i18n/locales/zh-tw.json | 1 + src/renderer/src/i18n/translate/el-gr.json | 1 + src/renderer/src/i18n/translate/es-es.json | 1 + src/renderer/src/i18n/translate/fr-fr.json | 1 + src/renderer/src/i18n/translate/pt-pt.json | 1 + src/renderer/src/pages/home/components/SelectModelButton.tsx | 5 ++++- 11 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/Preview/MermaidPreview.tsx b/src/renderer/src/components/Preview/MermaidPreview.tsx index 4c41e3c4b9..60708fbee8 100644 --- a/src/renderer/src/components/Preview/MermaidPreview.tsx +++ b/src/renderer/src/components/Preview/MermaidPreview.tsx @@ -56,6 +56,7 @@ const MermaidPreview = ({ document.body.removeChild(measureEl) } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [diagramId, mermaid, forceRenderKey] ) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index c6b745ac85..365b0797d1 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1552,6 +1552,7 @@ "selected": "Selected tags" }, "function_calling": "Function Calling", + "invalid_model": "Invalid Model", "no_matches": "No models available", "parameter_name": "Parameter Name", "parameter_type": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index a00750d71e..51a9f0d799 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1552,6 +1552,7 @@ "selected": "選択済みのタグ" }, "function_calling": "関数呼び出し", + "invalid_model": "無効なモデル", "no_matches": "利用可能なモデルがありません", "parameter_name": "パラメータ名", "parameter_type": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index d6c6d4d198..0fc5d567ac 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1552,6 +1552,7 @@ "selected": "Выбранные теги" }, "function_calling": "Вызов функции", + "invalid_model": "Недействительная модель", "no_matches": "Нет доступных моделей", "parameter_name": "Имя параметра", "parameter_type": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index bf3215a35c..feb7338be3 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1552,6 +1552,7 @@ "selected": "已选标签" }, "function_calling": "函数调用", + "invalid_model": "无效模型", "no_matches": "无可用模型", "parameter_name": "参数名称", "parameter_type": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 93881b95f9..b8337ac2c1 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1552,6 +1552,7 @@ "selected": "已選標籤" }, "function_calling": "函數調用", + "invalid_model": "無效模型", "no_matches": "無可用模型", "parameter_name": "參數名稱", "parameter_type": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 43602afe6f..e5cb29ec8a 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -1550,6 +1550,7 @@ "selected": "Επιλεγμένη ετικέτα" }, "function_calling": "Ξεχωριστική Κλήση Συναρτήσεων", + "invalid_model": "Μη έγκυρο μοντέλο", "no_matches": "Δεν υπάρχουν διαθέσιμα μοντέλα", "parameter_name": "Όνομα παραμέτρου", "parameter_type": { diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 715401149f..6924a3731c 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -1550,6 +1550,7 @@ "selected": "Etiquetas seleccionadas" }, "function_calling": "Llamada a función", + "invalid_model": "Modelo inválido", "no_matches": "No hay modelos disponibles", "parameter_name": "Nombre del parámetro", "parameter_type": { diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 794c76a35e..d3e216b809 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -1550,6 +1550,7 @@ "selected": "Étiquette sélectionnée" }, "function_calling": "Appel de fonction", + "invalid_model": "Modèle invalide", "no_matches": "Aucun modèle disponible", "parameter_name": "Nom du paramètre", "parameter_type": { diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 57d26464e8..1595318214 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -1550,6 +1550,7 @@ "selected": "Etiqueta selecionada" }, "function_calling": "Chamada de função", + "invalid_model": "Modelo inválido", "no_matches": "Nenhum modelo disponível", "parameter_name": "Nome do parâmetro", "parameter_type": { diff --git a/src/renderer/src/pages/home/components/SelectModelButton.tsx b/src/renderer/src/pages/home/components/SelectModelButton.tsx index 214ceb7185..bd6af86f7b 100644 --- a/src/renderer/src/pages/home/components/SelectModelButton.tsx +++ b/src/renderer/src/pages/home/components/SelectModelButton.tsx @@ -4,8 +4,9 @@ import { isLocalAi } from '@renderer/config/env' import { isEmbeddingModel, isRerankModel, isWebSearchModel } from '@renderer/config/models' import { useAssistant } from '@renderer/hooks/useAssistant' import { getProviderName } from '@renderer/services/ProviderService' +import { useAppSelector } from '@renderer/store' import { Assistant, Model } from '@renderer/types' -import { Button } from 'antd' +import { Button, Tag } from 'antd' import { ChevronsUpDown } from 'lucide-react' import { FC, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' @@ -19,6 +20,7 @@ const SelectModelButton: FC = ({ assistant }) => { const { model, updateAssistant } = useAssistant(assistant.id) const { t } = useTranslation() const timerRef = useRef(undefined) + const provider = useAppSelector((state) => state.llm.providers.find((p) => p.id === model?.provider)) const modelFilter = (model: Model) => !isEmbeddingModel(model) && !isRerankModel(model) @@ -60,6 +62,7 @@ const SelectModelButton: FC = ({ assistant }) => { + {!provider && {t('models.invalid_model')}} ) }