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/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/Tab/TabContainer.tsx b/src/renderer/src/components/Tab/TabContainer.tsx index 271bd8fcdc..2a9f04bf37 100644 --- a/src/renderer/src/components/Tab/TabContainer.tsx +++ b/src/renderer/src/components/Tab/TabContainer.tsx @@ -1,4 +1,5 @@ import { PlusOutlined } from '@ant-design/icons' +import { Sortable, useDndReorder } from '@renderer/components/dnd' import { isLinux, isMac, isWin } from '@renderer/config/constant' import { useTheme } from '@renderer/context/ThemeProvider' import { useFullscreen } from '@renderer/hooks/useFullscreen' @@ -7,7 +8,7 @@ import { getThemeModeLabel, getTitleLabel } from '@renderer/i18n/label' import tabsService from '@renderer/services/TabsService' import { useAppDispatch, useAppSelector } from '@renderer/store' import type { Tab } from '@renderer/store/tabs' -import { addTab, removeTab, reorderTabs, setActiveTab } from '@renderer/store/tabs' +import { addTab, removeTab, setActiveTab, setTabs } from '@renderer/store/tabs' import { ThemeMode } from '@renderer/types' import { classNames } from '@renderer/utils' import { Tooltip } from 'antd' @@ -28,7 +29,7 @@ import { Terminal, X } from 'lucide-react' -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useLocation, useNavigate } from 'react-router-dom' import styled from 'styled-components' @@ -81,11 +82,6 @@ const TabsContainer: React.FC = ({ children }) => { const { settedTheme, toggleTheme } = useTheme() const { hideMinappPopup } = useMinappPopup() const { t } = useTranslation() - const [dragState, setDragState] = useState<{ - isDragging: boolean - dragIndex: number - dragOverIndex: number - }>({ isDragging: false, dragIndex: -1, dragOverIndex: -1 }) const getTabId = (path: string): string => { if (path === '/') return 'home' @@ -147,88 +143,48 @@ const TabsContainer: React.FC = ({ children }) => { navigate(tab.path) } - const handleDragStart = (e: React.DragEvent, index: number) => { - if (tabs[index].id === 'home') { - e.preventDefault() - return - } - setDragState({ isDragging: true, dragIndex: index, dragOverIndex: -1 }) - e.dataTransfer.effectAllowed = 'move' - e.dataTransfer.setData('text/plain', index.toString()) - } + const filteredTabs = useMemo(() => tabs.filter((tab) => !specialTabs.includes(tab.id)), [tabs]) - const handleDragOver = (e: React.DragEvent, index: number) => { - if (tabs[index].id === 'home') return - e.preventDefault() - setDragState((prev) => ({ ...prev, dragOverIndex: index })) - } - - const handleDragLeave = () => { - setDragState((prev) => ({ ...prev, dragOverIndex: -1 })) - } - - const handleDrop = (e: React.DragEvent, dropIndex: number) => { - e.preventDefault() - const dragIndex = dragState.dragIndex - - if (dragIndex !== -1 && dragIndex !== dropIndex && tabs[dragIndex].id !== 'home' && tabs[dropIndex].id !== 'home') { - dispatch(reorderTabs({ fromIndex: dragIndex, toIndex: dropIndex })) - } - - setDragState({ isDragging: false, dragIndex: -1, dragOverIndex: -1 }) - } - - const handleDragEnd = () => { - setDragState({ isDragging: false, dragIndex: -1, dragOverIndex: -1 }) - } - - const visibleTabs = tabs.filter((tab) => !specialTabs.includes(tab.id)) + const { onSortEnd } = useDndReorder({ + originalList: tabs, + filteredList: filteredTabs, + onUpdate: (newTabs) => dispatch(setTabs(newTabs)), + itemKey: 'id' + }) return ( - - {visibleTabs.map((tab, index) => { - const isDragOver = dragState.dragOverIndex === index - const isDragging = dragState.isDragging && dragState.dragIndex === index - const canDrag = tab.id !== 'home' - - return ( - handleTabClick(tab)} - draggable={canDrag} - onDragStart={(e) => handleDragStart(e, index)} - onDragOver={(e) => handleDragOver(e, index)} - onDragLeave={handleDragLeave} - onDrop={(e) => handleDrop(e, index)} - onDragEnd={handleDragEnd} - $isDragOver={isDragOver} - $isDragging={isDragging} - $canDrag={canDrag} - $tabCount={visibleTabs.length}> - - {tab.id && {getTabIcon(tab.id)}} - {getTitleLabel(tab.id)} - - {tab.id !== 'home' && ( - { - e.stopPropagation() - closeTab(tab.id) - }}> - - - )} - - ) - })} - - - - + ( + handleTabClick(tab)}> + + {tab.id && {getTabIcon(tab.id)}} + {getTitleLabel(tab.id)} + + {tab.id !== 'home' && ( + { + e.stopPropagation() + closeTab(tab.id) + }}> + + + )} + + )} + /> + + + ` } ` -const TabsWrapper = styled.div<{ $tabCount: number }>` - display: flex; - flex-direction: row; - align-items: center; - gap: 5px; - flex: 1; - overflow-x: auto; - overflow-y: hidden; - min-width: 0; - margin-right: 10px; - -webkit-app-region: drag; - - > * { - position: relative; - z-index: 1; - -webkit-app-region: no-drag; - } - - &::-webkit-scrollbar { - height: 0; - } -` - const Tab = styled.div<{ active?: boolean $isDragOver?: boolean diff --git a/src/renderer/src/components/dnd/Sortable.tsx b/src/renderer/src/components/dnd/Sortable.tsx index a3102748cc..3ef77acb31 100644 --- a/src/renderer/src/components/dnd/Sortable.tsx +++ b/src/renderer/src/components/dnd/Sortable.tsx @@ -56,6 +56,8 @@ interface SortableProps { listStyle?: React.CSSProperties /** Ghost item style */ ghostItemStyle?: React.CSSProperties + /** Item gap */ + gap?: number | string } function Sortable({ @@ -70,7 +72,8 @@ function Sortable({ useDragOverlay = true, showGhost = false, className, - listStyle + listStyle, + gap }: SortableProps) { const sensors = useSensors( useSensor(PortalSafePointerSensor, { @@ -150,7 +153,12 @@ function Sortable({ onDragCancel={handleDragCancel} modifiers={modifiers}> - + {items.map((item, index) => ( ({ ) } -const ListWrapper = styled.div` +const ListWrapper = styled.div<{ $gap?: number | string }>` + gap: ${({ $gap }) => $gap}; + &[data-layout='grid'] { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); width: 100%; - gap: 12px; @media (max-width: 768px) { grid-template-columns: 1fr; } } + + &[data-layout='list'] { + display: flex; + align-items: center; + + [data-direction='horizontal'] { + flex-direction: row; + } + + [data-direction='vertical'] { + flex-direction: column; + } + } ` export default Sortable 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/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 ? ( ) : ( 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} + ) : ( { originalList: mcpServers, filteredList: filteredMcpServers, onUpdate: updateMcpServers, - idKey: 'id' + itemKey: 'id' }) const scrollRef = useRef(null) @@ -251,6 +251,7 @@ const McpServersList: FC = () => { itemKey="id" onSortEnd={onSortEnd} layout="grid" + gap={'12px'} useDragOverlay showGhost renderItem={(server) => ( 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(() => { diff --git a/src/renderer/src/store/tabs.ts b/src/renderer/src/store/tabs.ts index 92803358f3..df8f3e6a8f 100644 --- a/src/renderer/src/store/tabs.ts +++ b/src/renderer/src/store/tabs.ts @@ -24,6 +24,9 @@ const tabsSlice = createSlice({ name: 'tabs', initialState, reducers: { + setTabs: (state, action: PayloadAction) => { + state.tabs = action.payload + }, addTab: (state, action: PayloadAction) => { const existingTab = state.tabs.find((tab) => tab.path === action.payload.path) if (!existingTab) { @@ -46,25 +49,12 @@ const tabsSlice = createSlice({ if (tab) { Object.assign(tab, action.payload.updates) } - }, + } setActiveTab: (state, action: PayloadAction) => { state.activeTabId = action.payload - }, - reorderTabs: (state, action: PayloadAction<{ fromIndex: number; toIndex: number }>) => { - const { fromIndex, toIndex } = action.payload - if ( - fromIndex !== toIndex && - fromIndex >= 0 && - toIndex >= 0 && - fromIndex < state.tabs.length && - toIndex < state.tabs.length - ) { - const [movedTab] = state.tabs.splice(fromIndex, 1) - state.tabs.splice(toIndex, 0, movedTab) - } } } }) -export const { addTab, removeTab, setActiveTab, updateTab, reorderTabs } = tabsSlice.actions +export const { setTabs, addTab, removeTab, setActiveTab, updateTab } = tabsSlice.actions export default tabsSlice.reducer