From fb9c23c500bfc0a51d1f81a358153c65cf0524a3 Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat <43230886+MyPrototypeWhat@users.noreply.github.com> Date: Thu, 27 Mar 2025 12:06:47 +0800 Subject: [PATCH] Perf/optimize rendering (#3923) * optimize useMessageOperations * chore: update dependencies and refactor React imports - Added @ant-design/v5-patch-for-react-19 and rc-virtual-list to package.json. - Updated React and ReactDOM types to version 19 in package.json and yarn.lock. - Refactored ReactDOM usage to createRoot in main.tsx for better compatibility with React 18+. - Changed useContext to use in SyntaxHighlighterProvider and ThemeProvider components. - Adjusted flex-direction in Messages components to column for improved layout. - Removed unused state in CodeBlock component. * refactor(Messages): enhance scrolling behavior and introduce scroll utilities - Added createScrollHandler and scrollToBottom utilities for improved scroll management. - Updated Messages component to utilize new scroll utilities for better user experience. - Refactored scroll handling logic to ensure smooth scrolling when new messages are added. - Changed containerRef type to HTMLElement for better type safety. * refactor(Messages): streamline message handling and introduce useTopicMessages hook - Removed direct message selection from useMessageOperations and created a new useTopicMessages hook for better separation of concerns. - Updated Messages component to utilize the new useTopicMessages hook for fetching messages. - Enhanced message display logic with computeDisplayMessages function for improved message rendering. - Refactored scrolling behavior to maintain a smooth user experience during message updates. * refactor(Message Operations): introduce useTopicLoading hook for improved loading state management - Removed loading state from useMessageOperations and created a new useTopicLoading hook for better separation of concerns. - Updated components to utilize the new useTopicLoading hook for fetching loading states related to topics. - Enhanced code organization and readability by streamlining message operations and loading state handling. * refactor(Messages): replace updateMessage with updateMessageThunk for improved async handling - Updated useMessageOperations and MessageAnchorLine components to utilize updateMessageThunk for message updates. - Enhanced error handling and database synchronization in the new thunk implementation. - Streamlined message update logic to improve code clarity and maintainability. * refactor(SyntaxHighlighterProvider, MessageTools, AddMcpServerPopup): update styles and improve type safety - Changed import statements to use TypeScript's type imports for better clarity and type safety. - Updated MessageTools and AddMcpServerPopup components to replace bodyStyle with styles prop for consistent styling approach. - Enhanced overall code organization and maintainability by adhering to TypeScript best practices. * refactor(Messages): update layout and remove unnecessary prop - Removed the hasChildren prop from the Messages component for cleaner code. - Adjusted flex-direction in the mini chat Messages component to column-reverse for improved layout consistency. * refactor: enhance type safety and component return types - Updated functional components to return React.ReactElement instead of JSX.Element for better type consistency. - Changed import statements to use TypeScript's type imports for improved clarity. - Initialized useRef hooks with null for better type safety in various components. - Adjusted props types to use HTMLAttributes for more accurate type definitions. * chore: update package dependencies - Removed outdated dependencies: @agentic/exa, @agentic/searxng, @agentic/tavily, and rc-virtual-list. - Added back @ant-design/v5-patch-for-react-19 and rc-virtual-list with specified versions for improved compatibility. * fix(useMessageOperations): ensure message retrieval from store when updating content --- package.json | 14 ++- src/renderer/src/App.tsx | 2 +- .../src/components/DragableList/index.tsx | 43 ++++---- src/renderer/src/components/app/Navbar.tsx | 5 +- .../src/context/StyleSheetManager.tsx | 4 +- .../src/context/SyntaxHighlighterProvider.tsx | 68 ++++++------ src/renderer/src/context/ThemeProvider.tsx | 4 +- .../src/hooks/useMessageOperations.ts | 45 ++++---- src/renderer/src/main.tsx | 9 +- .../src/pages/home/Inputbar/Inputbar.tsx | 9 +- .../src/pages/home/Markdown/CodeBlock.tsx | 15 ++- .../pages/home/Messages/ChatFlowHistory.tsx | 2 +- .../src/pages/home/Messages/Message.tsx | 2 +- .../pages/home/Messages/MessageAnchorLine.tsx | 18 ++-- .../pages/home/Messages/MessageMenubar.tsx | 20 ++-- .../src/pages/home/Messages/MessageTools.tsx | 12 ++- .../src/pages/home/Messages/Messages.tsx | 70 +++++++++--- .../src/pages/home/Tabs/TopicsTab.tsx | 2 +- .../src/pages/paintings/PaintingsPage.tsx | 7 +- .../MCPSettings/AddMcpServerPopup.tsx | 7 +- .../src/pages/settings/ShortcutSettings.tsx | 6 +- src/renderer/src/store/messages.ts | 65 +++++++---- src/renderer/src/utils/index.ts | 9 +- src/renderer/src/windows/mini/App.tsx | 2 +- yarn.lock | 101 +++++++----------- 25 files changed, 293 insertions(+), 248 deletions(-) diff --git a/package.json b/package.json index 36ae2c7f..4571179d 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "@agentic/exa": "^7.3.3", "@agentic/searxng": "^7.3.3", "@agentic/tavily": "^7.3.3", + "@ant-design/v5-patch-for-react-19": "^1.0.3", "@anthropic-ai/sdk": "^0.38.0", "@electron-toolkit/eslint-config-prettier": "^3.0.0", "@electron-toolkit/eslint-config-ts": "^3.0.0", @@ -114,8 +115,8 @@ "@types/md5": "^2.3.5", "@types/node": "^18.19.9", "@types/pako": "^1.0.2", - "@types/react": "^18.2.48", - "@types/react-dom": "^18.2.18", + "@types/react": "^19.0.12", + "@types/react-dom": "^19.0.4", "@types/react-infinite-scroll-component": "^5.0.0", "@types/tinycolor2": "^1", "@vitejs/plugin-react": "^4.2.1", @@ -149,8 +150,9 @@ "openai": "patch:openai@npm%3A4.77.3#~/.yarn/patches/openai-npm-4.77.3-59c6d42e7a.patch", "p-queue": "^8.1.0", "prettier": "^3.5.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "rc-virtual-list": "^3.18.5", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-hotkeys-hook": "^4.6.1", "react-i18next": "^14.1.2", "react-infinite-scroll-component": "^6.1.0", @@ -177,10 +179,6 @@ "uuid": "^10.0.0", "vite": "^5.0.12" }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, "resolutions": { "pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch", "@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch", diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index e820866b..c5e70a92 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -21,7 +21,7 @@ import PaintingsPage from './pages/paintings/PaintingsPage' import SettingsPage from './pages/settings/SettingsPage' import TranslatePage from './pages/translate/TranslatePage' -function App(): JSX.Element { +function App(): React.ReactElement { return ( diff --git a/src/renderer/src/components/DragableList/index.tsx b/src/renderer/src/components/DragableList/index.tsx index 05ed09c4..a5c2db91 100644 --- a/src/renderer/src/components/DragableList/index.tsx +++ b/src/renderer/src/components/DragableList/index.tsx @@ -8,6 +8,7 @@ import { OnDragStartResponder, ResponderProvided } from '@hello-pangea/dnd' +import VirtualList from 'rc-virtual-list' import { droppableReorder } from '@renderer/utils' import { FC } from 'react' @@ -47,26 +48,28 @@ const DragableList: FC> = ({ {(provided) => (
- {list.map((item, index) => { - const id = item.id || item - return ( - - {(provided) => ( -
- {children(item, index)} -
- )} -
- ) - })} + + {(item, index) => { + const id = item.id || item + return ( + + {(provided) => ( +
+ {children(item, index)} +
+ )} +
+ ) + }} +
{provided.placeholder}
)} diff --git a/src/renderer/src/components/app/Navbar.tsx b/src/renderer/src/components/app/Navbar.tsx index 47c1b3f3..db6ed9fa 100644 --- a/src/renderer/src/components/app/Navbar.tsx +++ b/src/renderer/src/components/app/Navbar.tsx @@ -1,9 +1,10 @@ import { isMac } from '@renderer/config/constant' import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor' -import { FC, PropsWithChildren } from 'react' +import type { FC, PropsWithChildren } from 'react' +import type { HTMLAttributes } from 'react' import styled from 'styled-components' -type Props = PropsWithChildren & JSX.IntrinsicElements['div'] +type Props = PropsWithChildren & HTMLAttributes export const Navbar: FC = ({ children, ...props }) => { const backgroundColor = useNavBackgroundColor() diff --git a/src/renderer/src/context/StyleSheetManager.tsx b/src/renderer/src/context/StyleSheetManager.tsx index 04083c79..64c506bf 100644 --- a/src/renderer/src/context/StyleSheetManager.tsx +++ b/src/renderer/src/context/StyleSheetManager.tsx @@ -1,12 +1,12 @@ import isPropValid from '@emotion/is-prop-valid' -import { ReactNode } from 'react' +import type { ReactNode } from 'react' import { StyleSheetManager as StyledComponentsStyleSheetManager } from 'styled-components' interface StyleSheetManagerProps { children: ReactNode } -const StyleSheetManager = ({ children }: StyleSheetManagerProps): JSX.Element => { +const StyleSheetManager = ({ children }: StyleSheetManagerProps): React.ReactElement => { return ( { diff --git a/src/renderer/src/context/SyntaxHighlighterProvider.tsx b/src/renderer/src/context/SyntaxHighlighterProvider.tsx index cbdee21d..bbb4624d 100644 --- a/src/renderer/src/context/SyntaxHighlighterProvider.tsx +++ b/src/renderer/src/context/SyntaxHighlighterProvider.tsx @@ -1,16 +1,11 @@ import { useTheme } from '@renderer/context/ThemeProvider' import { useMermaid } from '@renderer/hooks/useMermaid' import { useSettings } from '@renderer/hooks/useSettings' -import { CodeStyleVarious, ThemeMode } from '@renderer/types' -import React, { createContext, PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react' -import { - BundledLanguage, - bundledLanguages, - BundledTheme, - bundledThemes, - createHighlighter, - HighlighterGeneric -} from 'shiki' +import { type CodeStyleVarious, ThemeMode } from '@renderer/types' +import type React from 'react' +import { createContext, type PropsWithChildren, use, useCallback, useEffect, useMemo, useState } from 'react' +import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki' +import { bundledLanguages, bundledThemes, createHighlighter } from 'shiki' interface SyntaxHighlighterContextType { codeToHtml: (code: string, language: string) => Promise @@ -51,42 +46,47 @@ export const SyntaxHighlighterProvider: React.FC = ({ childre initHighlighter() }, [highlighterTheme]) - const codeToHtml = async (code: string, language: string) => { - if (!highlighter) return '' + const codeToHtml = useCallback( + async (_code: string, language: string) => { + { + if (!highlighter) return '' - const languageMap: Record = { - vab: 'vb' - } + const languageMap: Record = { + vab: 'vb' + } - const mappedLanguage = languageMap[language] || language + const mappedLanguage = languageMap[language] || language - code = code?.trimEnd() ?? '' - const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '<', '>': '>' })[char]!) + const code = _code?.trimEnd() ?? '' + const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '<', '>': '>' })[char]!) - try { - if (!highlighter.getLoadedLanguages().includes(mappedLanguage as BundledLanguage)) { - if (mappedLanguage in bundledLanguages || mappedLanguage === 'text') { - await highlighter.loadLanguage(mappedLanguage as BundledLanguage) - } else { + try { + if (!highlighter.getLoadedLanguages().includes(mappedLanguage as BundledLanguage)) { + if (mappedLanguage in bundledLanguages || mappedLanguage === 'text') { + await highlighter.loadLanguage(mappedLanguage as BundledLanguage) + } else { + return `
${escapedCode}
` + } + } + + return highlighter.codeToHtml(code, { + lang: mappedLanguage, + theme: highlighterTheme + }) + } catch (error) { + console.warn(`Error highlighting code for language '${mappedLanguage}':`, error) return `
${escapedCode}
` } } - - return highlighter.codeToHtml(code, { - lang: mappedLanguage, - theme: highlighterTheme - }) - } catch (error) { - console.warn(`Error highlighting code for language '${mappedLanguage}':`, error) - return `
${escapedCode}
` - } - } + }, + [highlighter, highlighterTheme] + ) return {children} } export const useSyntaxHighlighter = () => { - const context = useContext(SyntaxHighlighterContext) + const context = use(SyntaxHighlighterContext) if (!context) { throw new Error('useSyntaxHighlighter must be used within a SyntaxHighlighterProvider') } diff --git a/src/renderer/src/context/ThemeProvider.tsx b/src/renderer/src/context/ThemeProvider.tsx index 5a3fd812..113898f4 100644 --- a/src/renderer/src/context/ThemeProvider.tsx +++ b/src/renderer/src/context/ThemeProvider.tsx @@ -1,7 +1,7 @@ import { isMac } from '@renderer/config/constant' import { useSettings } from '@renderer/hooks/useSettings' import { ThemeMode } from '@renderer/types' -import React, { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react' +import React, { createContext, PropsWithChildren, use, useEffect, useState } from 'react' interface ThemeContextType { theme: ThemeMode @@ -64,4 +64,4 @@ export const ThemeProvider: React.FC = ({ children, defaultT ) } -export const useTheme = () => useContext(ThemeContext) +export const useTheme = () => use(ThemeContext) diff --git a/src/renderer/src/hooks/useMessageOperations.ts b/src/renderer/src/hooks/useMessageOperations.ts index fe863d5b..05d072c7 100644 --- a/src/renderer/src/hooks/useMessageOperations.ts +++ b/src/renderer/src/hooks/useMessageOperations.ts @@ -5,14 +5,15 @@ import { clearStreamMessage, clearTopicMessages, commitStreamMessage, + deleteMessageAction, resendMessage, selectDisplayCount, selectTopicLoading, selectTopicMessages, setStreamMessage, setTopicLoading, - updateMessage, - updateMessages + updateMessages, + updateMessageThunk } from '@renderer/store/messages' import type { Assistant, Message, Topic } from '@renderer/types' import { abortCompletion } from '@renderer/utils/abortController' @@ -27,17 +28,15 @@ import { TopicManager } from './useTopic' */ export function useMessageOperations(topic: Topic) { const dispatch = useAppDispatch() - const messages = useAppSelector((state) => selectTopicMessages(state, topic.id)) /** * 删除单个消息 */ const deleteMessage = useCallback( - async (message: Message) => { - const newMessages = messages.filter((m) => m.id !== message.id) - await dispatch(updateMessages(topic, newMessages)) + async (id: string) => { + await dispatch(deleteMessageAction(topic, id)) }, - [dispatch, topic, messages] + [dispatch, topic] ) /** @@ -45,10 +44,9 @@ export function useMessageOperations(topic: Topic) { */ const deleteGroupMessages = useCallback( async (askId: string) => { - const newMessages = messages.filter((m) => m.askId !== askId) - await dispatch(updateMessages(topic, newMessages)) + await dispatch(deleteMessageAction(topic, askId, 'askId')) }, - [dispatch, topic, messages] + [dispatch, topic] ) /** @@ -58,23 +56,17 @@ export function useMessageOperations(topic: Topic) { async (messageId: string, updates: Partial) => { // 如果更新包含内容变更,重新计算 token if ('content' in updates) { - const message = messages.find((m) => m.id === messageId) + const messages = store.getState().messages.messagesByTopic[topic.id] + const message = messages?.find((m) => m.id === messageId) if (message) { const updatedMessage = { ...message, ...updates } const usage = await estimateMessageUsage(updatedMessage) updates.usage = usage } } - - await dispatch( - updateMessage({ - topicId: topic.id, - messageId, - updates - }) - ) + await dispatch(updateMessageThunk(topic.id, messageId, updates)) }, - [dispatch, topic.id, messages] + [dispatch, topic.id] ) /** @@ -159,7 +151,6 @@ export function useMessageOperations(topic: Topic) { EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT) }, []) - const loading = useAppSelector((state) => selectTopicLoading(state, topic.id)) const displayCount = useAppSelector(selectDisplayCount) // /** // * 获取当前消息列表 @@ -211,8 +202,6 @@ export function useMessageOperations(topic: Topic) { ) return { - messages, - loading, displayCount, updateMessages: updateMessagesAction, deleteMessage, @@ -230,3 +219,13 @@ export function useMessageOperations(topic: Topic) { resumeMessage } } + +export const useTopicMessages = (topic: Topic) => { + const messages = useAppSelector((state) => selectTopicMessages(state, topic.id)) + return messages +} + +export const useTopicLoading = (topic: Topic) => { + const loading = useAppSelector((state) => selectTopicLoading(state, topic.id)) + return loading +} diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 4885d60b..e9b96451 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -1,13 +1,16 @@ import './assets/styles/index.scss' +import '@ant-design/v5-patch-for-react-19' -import ReactDOM from 'react-dom/client' +import { createRoot } from 'react-dom/client' import App from './App' import MiniApp from './windows/mini/App' if (location.hash === '#/mini') { document.getElementById('spinner')?.remove() - ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render() + const root = createRoot(document.getElementById('root') as HTMLElement) + root.render() } else { - ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render() + const root = createRoot(document.getElementById('root') as HTMLElement) + root.render() } diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 1dadb054..5fe831bf 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -13,7 +13,7 @@ import TranslateButton from '@renderer/components/TranslateButton' import { isFunctionCallingModel, isGenerateImageModel, isVisionModel, isWebSearchModel } from '@renderer/config/models' import db from '@renderer/databases' import { useAssistant } from '@renderer/hooks/useAssistant' -import { useMessageOperations } from '@renderer/hooks/useMessageOperations' +import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations' import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings' import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts' @@ -83,10 +83,11 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const containerRef = useRef(null) const { searching } = useRuntime() const { isBubbleStyle } = useMessageStyle() - const { loading, pauseMessages } = useMessageOperations(topic) + const { pauseMessages } = useMessageOperations(topic) + const loading = useTopicLoading(topic) const dispatch = useAppDispatch() const [spaceClickCount, setSpaceClickCount] = useState(0) - const spaceClickTimer = useRef() + const spaceClickTimer = useRef(null) const [isTranslating, setIsTranslating] = useState(false) const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState([]) const [mentionModels, setMentionModels] = useState([]) @@ -96,7 +97,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const [textareaHeight, setTextareaHeight] = useState() const startDragY = useRef(0) const startHeight = useRef(0) - const currentMessageId = useRef() + const currentMessageId = useRef('') const isVision = useMemo(() => isVisionModel(model), [model]) const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision]) const navigate = useNavigate() diff --git a/src/renderer/src/pages/home/Markdown/CodeBlock.tsx b/src/renderer/src/pages/home/Markdown/CodeBlock.tsx index 764a3eee..2975f35d 100644 --- a/src/renderer/src/pages/home/Markdown/CodeBlock.tsx +++ b/src/renderer/src/pages/home/Markdown/CodeBlock.tsx @@ -26,7 +26,7 @@ const CodeBlock: React.FC = ({ children, className }) => { const match = /language-(\w+)/.exec(className || '') || children?.includes('\n') const { codeShowLineNumbers, fontSize, codeCollapsible, codeWrappable } = useSettings() const language = match?.[1] ?? 'text' - const [html, setHtml] = useState('') + // const [html, setHtml] = useState('') const { codeToHtml } = useSyntaxHighlighter() const [isExpanded, setIsExpanded] = useState(!codeCollapsible) const [isUnwrapped, setIsUnwrapped] = useState(!codeWrappable) @@ -40,17 +40,14 @@ const CodeBlock: React.FC = ({ children, className }) => { useEffect(() => { const loadHighlightedCode = async () => { const highlightedHtml = await codeToHtml(children, language) - setHtml(highlightedHtml) + if (codeContentRef.current) { + codeContentRef.current.innerHTML = highlightedHtml + setShouldShowExpandButton(codeContentRef.current.scrollHeight > 350) + } } loadHighlightedCode() }, [children, language, codeToHtml]) - useEffect(() => { - if (codeContentRef.current) { - setShouldShowExpandButton(codeContentRef.current.scrollHeight > 350) - } - }, [html]) - useEffect(() => { if (!codeCollapsible) { setIsExpanded(true) @@ -112,7 +109,7 @@ const CodeBlock: React.FC = ({ children, className }) => { isShowLineNumbers={codeShowLineNumbers} isUnwrapped={isUnwrapped} isCodeWrappable={codeWrappable} - dangerouslySetInnerHTML={{ __html: html }} + // dangerouslySetInnerHTML={{ __html: html }} style={{ border: '0.5px solid var(--color-code-background)', borderTopLeftRadius: 0, diff --git a/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx b/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx index a9bf4502..8157ed1d 100644 --- a/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx +++ b/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx @@ -50,7 +50,7 @@ const CustomNode: FC<{ data: any }> = ({ data }) => { let title = '' let backgroundColor = 'var(--bg-color)' let gradientColor = 'rgba(0, 0, 0, 0.03)' - let avatar: JSX.Element | null = null + let avatar: React.ReactNode | null = null // 根据消息类型设置不同的样式和图标 if (nodeType === 'user') { diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index 39350721..818511cb 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -163,7 +163,7 @@ const MessageItem: FC = ({ isLastMessage={isLastMessage} isAssistantMessage={isAssistantMessage} isGrouped={isGrouped} - messageContainerRef={messageContainerRef} + messageContainerRef={messageContainerRef as React.RefObject} setModel={setModel} /> diff --git a/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx b/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx index be3c64fa..be70d8f4 100644 --- a/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx +++ b/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx @@ -6,11 +6,11 @@ import { useSettings } from '@renderer/hooks/useSettings' import { getMessageModelId } from '@renderer/services/MessagesService' import { getModelName } from '@renderer/services/ModelService' import { useAppDispatch } from '@renderer/store' -import { updateMessage } from '@renderer/store/messages' -import { Message } from '@renderer/types' +import { updateMessageThunk } from '@renderer/store/messages' +import type { Message } from '@renderer/types' import { isEmoji, removeLeadingEmoji } from '@renderer/utils' import { Avatar } from 'antd' -import { FC, useCallback, useEffect, useRef, useState } from 'react' +import { type FC, useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' interface MessageLineProps { @@ -100,15 +100,9 @@ const MessageAnchorLine: FC = ({ messages }) => { (message: Message) => { const groupMessages = messages.filter((m) => m.askId === message.askId) if (groupMessages.length > 1) { - groupMessages.forEach((m) => { - dispatch( - updateMessage({ - topicId: m.topicId, - messageId: m.id, - updates: { foldSelected: m.id === message.id } - }) - ) - }) + for (const m of groupMessages) { + dispatch(updateMessageThunk(m.topicId, m.id, { foldSelected: m.id === message.id })) + } setTimeout(() => { const messageElement = document.getElementById(`message-${message.id}`) diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 5b518524..220a5bec 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -17,12 +17,12 @@ import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup' import TextEditPopup from '@renderer/components/Popups/TextEditPopup' import { isReasoningModel } from '@renderer/config/models' import { TranslateLanguageOptions } from '@renderer/config/translate' -import { useMessageOperations } from '@renderer/hooks/useMessageOperations' +import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { getMessageTitle, resetAssistantMessage } from '@renderer/services/MessagesService' import { translateText } from '@renderer/services/TranslateService' -import { Message, Model } from '@renderer/types' -import { Assistant, Topic } from '@renderer/types' +import type { Message, Model } from '@renderer/types' +import type { Assistant, Topic } from '@renderer/types' import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, removeTrailingDoubleSpaces } from '@renderer/utils' import { exportMarkdownToJoplin, @@ -62,15 +62,9 @@ const MessageMenubar: FC = (props) => { const [showRegenerateTooltip, setShowRegenerateTooltip] = useState(false) const [showDeleteTooltip, setShowDeleteTooltip] = useState(false) const assistantModel = assistant?.model - const { - loading, - editMessage, - setStreamMessage, - deleteMessage, - resendMessage, - commitStreamMessage, - clearStreamMessage - } = useMessageOperations(topic) + const { editMessage, setStreamMessage, deleteMessage, resendMessage, commitStreamMessage, clearStreamMessage } = + useMessageOperations(topic) + const loading = useTopicLoading(topic) const isUserMessage = message.role === 'user' @@ -382,7 +376,7 @@ const MessageMenubar: FC = (props) => { okButtonProps={{ danger: true }} icon={} onOpenChange={(open) => open && setShowDeleteTooltip(false)} - onConfirm={() => deleteMessage(message)}> + onConfirm={() => deleteMessage(message.id)}> e.stopPropagation()}> = ({ message }) => { // Format tool responses for collapse items const getCollapseItems = () => { - const items: { key: string; label: JSX.Element; children: React.ReactNode }[] = [] + const items: { key: string; label: React.ReactNode; children: React.ReactNode }[] = [] // Add tool responses - toolResponses.forEach((toolResponse: MCPToolResponse) => { + for (const toolResponse of toolResponses) { const { id, tool, status, response } = toolResponse const isInvoking = status === 'invoking' const isDone = status === 'done' @@ -105,7 +105,7 @@ const MessageTools: FC = ({ message }) => { ) }) - }) + } return items } @@ -129,7 +129,9 @@ const MessageTools: FC = ({ message }) => { onCancel={() => setExpandedResponse(null)} footer={null} width="80%" - bodyStyle={{ maxHeight: '80vh', overflow: 'auto' }}> + styles={{ + body: { maxHeight: '80vh', overflow: 'auto' } + }}> {expandedResponse && ( void } +const computeDisplayMessages = (messages: Message[], startIndex: number, displayCount: number) => { + const reversedMessages = [...messages].reverse() + + // 如果剩余消息数量小于 displayCount,直接返回所有剩余消息 + if (reversedMessages.length - startIndex <= displayCount) { + return reversedMessages.slice(startIndex) + } + + const userIdSet = new Set() // 用户消息 id 集合 + const assistantIdSet = new Set() // 助手消息 askId 集合 + const displayMessages: Message[] = [] + + // 处理单条消息的函数 + const processMessage = (message: Message) => { + if (!message) return + + const idSet = message.role === 'user' ? userIdSet : assistantIdSet + const messageId = message.role === 'user' ? message.id : message.askId + + if (!idSet.has(messageId)) { + idSet.add(messageId) + displayMessages.push(message) + return + } + // 如果是相同 askId 的助手消息,也要显示 + displayMessages.push(message) + } + + // 遍历消息直到满足显示数量要求 + for (let i = startIndex; i < reversedMessages.length && userIdSet.size + assistantIdSet.size < displayCount; i++) { + processMessage(reversedMessages[i]) + } + + return displayMessages +} + const Messages: React.FC = ({ assistant, topic, setActiveTopic }) => { const { t } = useTranslation() const { showTopics, topicPosition, showAssistants, messageNavigation } = useSettings() @@ -48,9 +84,9 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) const [hasMore, setHasMore] = useState(false) const [isLoadingMore, setIsLoadingMore] = useState(false) const [isProcessingContext, setIsProcessingContext] = useState(false) - const { messages, displayCount, loading, updateMessages, clearTopicMessages, deleteMessage } = - useMessageOperations(topic) - + const messages = useTopicMessages(topic) + const { displayCount, updateMessages, clearTopicMessages, deleteMessage } = useMessageOperations(topic) + const loading = useTopicLoading(topic) const messagesRef = useRef(messages) useEffect(() => { @@ -58,9 +94,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) }, [messages]) useEffect(() => { - const reversedMessages = [...messages].reverse() - const newDisplayMessages = reversedMessages.slice(0, displayCount) - + const newDisplayMessages = computeDisplayMessages(messages, 0, displayCount) setDisplayMessages(newDisplayMessages) setHasMore(messages.length > displayCount) }, [messages, displayCount]) @@ -73,7 +107,15 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) }, [showAssistants, showTopics, topicPosition]) const scrollToBottom = useCallback(() => { - setTimeout(() => containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'auto' }), 50) + if (containerRef.current) { + requestAnimationFrame(() => { + if (containerRef.current) { + containerRef.current.scrollTo({ + top: containerRef.current.scrollHeight + }) + } + }) + } }, []) useEffect(() => { @@ -122,7 +164,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) const lastMessage = last(messages) if (lastMessage?.type === 'clear') { - await deleteMessage(lastMessage) + await deleteMessage(lastMessage.id) scrollToBottom() return } @@ -183,10 +225,9 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) setIsLoadingMore(true) setTimeout(() => { const currentLength = displayMessages.length - const reversedMessages = [...messages].reverse() - const moreMessages = reversedMessages.slice(currentLength, currentLength + LOAD_MORE_COUNT) + const newMessages = computeDisplayMessages(messages, currentLength, LOAD_MORE_COUNT) - setDisplayMessages((prev) => [...prev, ...moreMessages]) + setDisplayMessages((prev) => [...prev, ...newMessages]) setHasMore(currentLength + LOAD_MORE_COUNT < messages.length) setIsLoadingMore(false) }, 300) @@ -199,7 +240,6 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic }) window.message.success(t('message.copy.success')) } }) - return ( = ({ assistant, topic, setActiveTopic }) next={loadMoreMessages} hasMore={hasMore} loader={null} - inverse={true} - scrollableTarget="messages"> + scrollableTarget="messages" + inverse> diff --git a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx index 920a291c..514160f3 100644 --- a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx @@ -56,7 +56,7 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic const borderRadius = showTopicTime ? 12 : 'var(--list-item-border-radius)' const [deletingTopicId, setDeletingTopicId] = useState(null) - const deleteTimerRef = useRef() + const deleteTimerRef = useRef(null) const pendingTopics = useMemo(() => { return new Set() diff --git a/src/renderer/src/pages/paintings/PaintingsPage.tsx b/src/renderer/src/pages/paintings/PaintingsPage.tsx index 21d5169a..638d1005 100644 --- a/src/renderer/src/pages/paintings/PaintingsPage.tsx +++ b/src/renderer/src/pages/paintings/PaintingsPage.tsx @@ -23,11 +23,12 @@ import { translateText } from '@renderer/services/TranslateService' import { useAppDispatch } from '@renderer/store' import { DEFAULT_PAINTING } from '@renderer/store/paintings' import { setGenerating } from '@renderer/store/runtime' -import { FileType, Painting } from '@renderer/types' +import type { FileType, Painting } from '@renderer/types' import { getErrorMessage } from '@renderer/utils' import { Button, Input, InputNumber, Radio, Select, Slider, Switch, Tooltip } from 'antd' import TextArea from 'antd/es/input/TextArea' -import { FC, useEffect, useRef, useState } from 'react' +import type { FC } from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -243,7 +244,7 @@ const PaintingsPage: FC = () => { const { autoTranslateWithSpace } = useSettings() const [spaceClickCount, setSpaceClickCount] = useState(0) const [isTranslating, setIsTranslating] = useState(false) - const spaceClickTimer = useRef() + const spaceClickTimer = useRef(null) const translate = async () => { if (isTranslating) { diff --git a/src/renderer/src/pages/settings/MCPSettings/AddMcpServerPopup.tsx b/src/renderer/src/pages/settings/MCPSettings/AddMcpServerPopup.tsx index 6e8c912f..5a729237 100644 --- a/src/renderer/src/pages/settings/MCPSettings/AddMcpServerPopup.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/AddMcpServerPopup.tsx @@ -152,7 +152,12 @@ const PopupContainer: React.FC = ({ server, create, resolve }) => { width={600} transitionName="ant-move-down" centered - bodyStyle={{ maxHeight: '70vh', overflowY: 'auto' }}> + styles={{ + body: { + maxHeight: '70vh', + overflowY: 'auto' + } + }}>
{ {isEditing ? ( el && (inputRefs.current[record.key] = el)} + ref={(el) => { + if (el) { + inputRefs.current[record.key] = el + } + }} value={formatShortcut(shortcut)} placeholder={t('settings.shortcuts.press_shortcut')} onKeyDown={(e) => handleKeyDown(e, record)} diff --git a/src/renderer/src/store/messages.ts b/src/renderer/src/store/messages.ts index 5ee2173d..6e244b89 100644 --- a/src/renderer/src/store/messages.ts +++ b/src/renderer/src/store/messages.ts @@ -6,9 +6,9 @@ import { fetchChatCompletion } from '@renderer/services/ApiService' import { getAssistantMessage, resetAssistantMessage } from '@renderer/services/MessagesService' import type { AppDispatch, RootState } from '@renderer/store' import type { Assistant, Message, Topic } from '@renderer/types' -import { Model } from '@renderer/types' +import type { Model } from '@renderer/types' import { clearTopicQueue, getTopicQueue, waitForTopicQueue } from '@renderer/utils/queue' -import { cloneDeep, isEmpty, throttle } from 'lodash' +import { isEmpty, throttle } from 'lodash' export interface MessagesState { messagesByTopic: Record @@ -113,14 +113,10 @@ const messagesSlice = createSlice({ ) => { const { topicId, messageId, updates } = action.payload const topicMessages = state.messagesByTopic[topicId] - if (topicMessages) { const message = topicMessages.find((msg) => msg.id === messageId) if (message) { Object.assign(message, updates) - db.topics.update(topicId, { - messages: topicMessages.map((m) => (m.id === message.id ? cloneDeep(message) : cloneDeep(m))) - }) } } }, @@ -255,7 +251,7 @@ export const sendMessage = const isGroupedMessage = messageToReset.length > 1 const resetMessage = resetAssistantMessage(m, isGroupedMessage ? m.model : assistant.model) // 更新状态 - dispatch(updateMessage({ topicId: topic.id, messageId: m.id, updates: resetMessage })) + dispatch(updateMessageThunk(topic.id, m.id, resetMessage)) // 使用重置后的消息 return resetMessage }) @@ -263,7 +259,7 @@ export const sendMessage = const { model, id } = messageToReset const resetMessage = resetAssistantMessage(messageToReset, model) // 更新状态 - dispatch(updateMessage({ topicId: topic.id, messageId: id, updates: resetMessage })) + dispatch(updateMessageThunk(topic.id, id, resetMessage)) // 使用重置后的消息 assistantMessages.push(resetMessage) } @@ -396,10 +392,9 @@ export const sendMessage = } catch (error: any) { console.error('Error in chat completion:', error) dispatch( - updateMessage({ - topicId: topic.id, - messageId: assistantMessage.id, - updates: { status: 'error', error: { message: error.message } } + updateMessageThunk(topic.id, assistantMessage.id, { + status: 'error', + error: { message: error.message } }) ) dispatch(clearStreamMessage({ topicId: topic.id, messageId: assistantMessage.id })) @@ -448,10 +443,9 @@ export const resendMessage = const userMessage = topicMessages.find((m) => m.id === message.askId && m.role === 'user') if (!userMessage) { dispatch( - updateMessage({ - topicId: topic.id, - messageId: message.id, - updates: { status: 'error', error: { message: i18n.t('error.user_message_not_found') } } + updateMessageThunk(topic.id, message.id, { + status: 'error', + error: { message: i18n.t('error.user_message_not_found') } }) ) console.error(i18n.t('error.user_message_not_found')) @@ -521,6 +515,14 @@ export const clearTopicMessagesThunk = (topic: Topic) => async (dispatch: AppDis } } +export const deleteMessageAction = + (topic: Topic, id: string, idType: 'id' | 'askId' = 'id') => + async (dispatch: AppDispatch, getState: () => RootState) => { + const messages = getState().messages.messagesByTopic[topic.id] || [] + const newMessages = messages.filter((m) => m[idType] !== id) + await dispatch(updateMessages(topic, newMessages)) + } + // 修改的 updateMessages thunk,同时更新缓存 export const updateMessages = (topic: Topic, messages: Message[]) => async (dispatch: AppDispatch) => { try { @@ -534,6 +536,28 @@ export const updateMessages = (topic: Topic, messages: Message[]) => async (disp } } +// 新增一个 thunk 来处理消息更新 +export const updateMessageThunk = + (topicId: string, messageId: string, updates: Partial) => + async (dispatch: AppDispatch, getState: () => RootState) => { + try { + // 先更新 Redux 状态 + dispatch(updateMessage({ topicId, messageId, updates })) + + // 然后同步到数据库 + const state = getState() + const topicMessages = state.messages.messagesByTopic[topicId] + if (topicMessages) { + await db.topics.update(topicId, { + messages: topicMessages + }) + } + } catch (error) { + console.error('Failed to update message:', error) + dispatch(setError(error instanceof Error ? error.message : 'Failed to update message')) + } + } + // Selectors export const selectCurrentTopicId = (state: RootState): string | null => { const messagesState = state.messages @@ -546,11 +570,10 @@ export const selectTopicMessages = createSelector( ) // 获取特定话题的loading状态 -export const selectTopicLoading = (state: RootState, topicId?: string): boolean => { - const messagesState = state.messages as MessagesState - const currentTopicId = topicId || messagesState.currentTopic?.id || '' - return currentTopicId ? (messagesState.loadingByTopic[currentTopicId] ?? false) : false -} +export const selectTopicLoading = createSelector( + [(state: RootState) => state.messages.loadingByTopic, (_, topicId?: string) => topicId], + (loadingByTopic, topicId) => (topicId ? (loadingByTopic[topicId] ?? false) : false) +) export const selectDisplayCount = (state: RootState): number => { const messagesState = state.messages as MessagesState diff --git a/src/renderer/src/utils/index.ts b/src/renderer/src/utils/index.ts index 7684c78b..10679d92 100644 --- a/src/renderer/src/utils/index.ts +++ b/src/renderer/src/utils/index.ts @@ -306,7 +306,7 @@ export async function captureDiv(divRef: React.RefObject) { return Promise.resolve(undefined) } -export const captureScrollableDiv = async (divRef: React.RefObject) => { +export const captureScrollableDiv = async (divRef: React.RefObject) => { if (divRef.current) { try { const div = divRef.current @@ -392,7 +392,7 @@ export const captureScrollableDiv = async (divRef: React.RefObject) => { +export const captureScrollableDivAsDataURL = async (divRef: React.RefObject) => { return captureScrollableDiv(divRef).then((canvas) => { if (canvas) { return canvas.toDataURL('image/png') @@ -401,7 +401,10 @@ export const captureScrollableDivAsDataURL = async (divRef: React.RefObject, func: BlobCallback) => { +export const captureScrollableDivAsBlob = async ( + divRef: React.RefObject, + func: BlobCallback +) => { await captureScrollableDiv(divRef).then((canvas) => { canvas?.toBlob(func, 'image/png') }) diff --git a/src/renderer/src/windows/mini/App.tsx b/src/renderer/src/windows/mini/App.tsx index 56075c4f..17367a0c 100644 --- a/src/renderer/src/windows/mini/App.tsx +++ b/src/renderer/src/windows/mini/App.tsx @@ -9,7 +9,7 @@ import { SyntaxHighlighterProvider } from '../../context/SyntaxHighlighterProvid import { ThemeProvider } from '../../context/ThemeProvider' import HomeWindow from './home/HomeWindow' -function MiniWindow(): JSX.Element { +function MiniWindow(): React.ReactElement { return ( diff --git a/yarn.lock b/yarn.lock index 44752ba3..0b7d4fa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -165,6 +165,17 @@ __metadata: languageName: node linkType: hard +"@ant-design/v5-patch-for-react-19@npm:^1.0.3": + version: 1.0.3 + resolution: "@ant-design/v5-patch-for-react-19@npm:1.0.3" + peerDependencies: + antd: ">=5.22.6" + react: ">=19.0.0" + react-dom: ">=19.0.0" + checksum: 10c0/e3848c929b01a0a29a41e2886f489932e54d9665dd990c60c4b505e21740da2ef0db0d08f4dc7591651fa1f21b15227e7bb40f40280742225b4da74fd18cc899 + languageName: node + linkType: hard + "@anthropic-ai/sdk@npm:^0.38.0": version: 0.38.0 resolution: "@anthropic-ai/sdk@npm:0.38.0" @@ -3455,19 +3466,12 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*": - version: 15.7.14 - resolution: "@types/prop-types@npm:15.7.14" - checksum: 10c0/1ec775160bfab90b67a782d735952158c7e702ca4502968aa82565bd8e452c2de8601c8dfe349733073c31179116cf7340710160d3836aa8a1ef76d1532893b1 - languageName: node - linkType: hard - -"@types/react-dom@npm:^18.2.18": - version: 18.3.5 - resolution: "@types/react-dom@npm:18.3.5" +"@types/react-dom@npm:^19.0.4": + version: 19.0.4 + resolution: "@types/react-dom@npm:19.0.4" peerDependencies: - "@types/react": ^18.0.0 - checksum: 10c0/b163d35a6b32a79f5782574a7aeb12a31a647e248792bf437e6d596e2676961c394c5e3c6e91d1ce44ae90441dbaf93158efb4f051c0d61e2612f1cb04ce4faa + "@types/react": ^19.0.0 + checksum: 10c0/4e71853919b94df9e746a4bd73f8180e9ae13016333ce9c543dcba9f4f4c8fe6e28b038ca6ee61c24e291af8e03ca3bc5ded17c46dee938fcb32d71186fda7a3 languageName: node linkType: hard @@ -3480,7 +3484,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*": +"@types/react@npm:*, @types/react@npm:^19.0.12": version: 19.0.12 resolution: "@types/react@npm:19.0.12" dependencies: @@ -3489,16 +3493,6 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.2.48": - version: 18.3.19 - resolution: "@types/react@npm:18.3.19" - dependencies: - "@types/prop-types": "npm:*" - csstype: "npm:^3.0.2" - checksum: 10c0/236bfe0c4748ada1a640f13573eca3e0fc7c9d847b442947adb352b0718d6d285357fd84c33336c8ffb8cbfabc0d58a43a647c7fd79857fecd61fb58ab6f7918 - languageName: node - linkType: hard - "@types/responselike@npm:^1.0.0": version: 1.0.3 resolution: "@types/responselike@npm:1.0.3" @@ -3766,6 +3760,7 @@ __metadata: "@agentic/exa": "npm:^7.3.3" "@agentic/searxng": "npm:^7.3.3" "@agentic/tavily": "npm:^7.3.3" + "@ant-design/v5-patch-for-react-19": "npm:^1.0.3" "@anthropic-ai/sdk": "npm:^0.38.0" "@cherrystudio/embedjs": "npm:^0.1.28" "@cherrystudio/embedjs-libsql": "npm:^0.1.28" @@ -3804,8 +3799,8 @@ __metadata: "@types/md5": "npm:^2.3.5" "@types/node": "npm:^18.19.9" "@types/pako": "npm:^1.0.2" - "@types/react": "npm:^18.2.48" - "@types/react-dom": "npm:^18.2.18" + "@types/react": "npm:^19.0.12" + "@types/react-dom": "npm:^19.0.4" "@types/react-infinite-scroll-component": "npm:^5.0.0" "@types/tinycolor2": "npm:^1" "@vitejs/plugin-react": "npm:^4.2.1" @@ -3853,8 +3848,9 @@ __metadata: p-queue: "npm:^8.1.0" prettier: "npm:^3.5.3" proxy-agent: "npm:^6.5.0" - react: "npm:^18.2.0" - react-dom: "npm:^18.2.0" + rc-virtual-list: "npm:^3.18.5" + react: "npm:^19.0.0" + react-dom: "npm:^19.0.0" react-hotkeys-hook: "npm:^4.6.1" react-i18next: "npm:^14.1.2" react-infinite-scroll-component: "npm:^6.1.0" @@ -3884,9 +3880,6 @@ __metadata: vite: "npm:^5.0.12" webdav: "npm:^5.8.0" zipread: "npm:^1.3.3" - peerDependencies: - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 languageName: unknown linkType: soft @@ -9190,7 +9183,7 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": +"js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed @@ -9798,17 +9791,6 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.1.0": - version: 1.4.0 - resolution: "loose-envify@npm:1.4.0" - dependencies: - js-tokens: "npm:^3.0.0 || ^4.0.0" - bin: - loose-envify: cli.js - checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e - languageName: node - linkType: hard - "lop@npm:^0.4.1": version: 0.4.2 resolution: "lop@npm:0.4.2" @@ -13272,7 +13254,7 @@ __metadata: languageName: node linkType: hard -"rc-virtual-list@npm:^3.14.2, rc-virtual-list@npm:^3.5.1, rc-virtual-list@npm:^3.5.2": +"rc-virtual-list@npm:^3.14.2, rc-virtual-list@npm:^3.18.5, rc-virtual-list@npm:^3.5.1, rc-virtual-list@npm:^3.5.2": version: 3.18.5 resolution: "rc-virtual-list@npm:3.18.5" dependencies: @@ -13301,15 +13283,14 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^18.2.0": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" +"react-dom@npm:^19.0.0": + version: 19.0.0 + resolution: "react-dom@npm:19.0.0" dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" + scheduler: "npm:^0.25.0" peerDependencies: - react: ^18.3.1 - checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 + react: ^19.0.0 + checksum: 10c0/a36ce7ab507b237ae2759c984cdaad4af4096d8199fb65b3815c16825e5cfeb7293da790a3fc2184b52bfba7ba3ff31c058c01947aff6fd1a3701632aabaa6a9 languageName: node linkType: hard @@ -13480,12 +13461,10 @@ __metadata: languageName: node linkType: hard -"react@npm:^18.2.0": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 +"react@npm:^19.0.0": + version: 19.0.0 + resolution: "react@npm:19.0.0" + checksum: 10c0/9cad8f103e8e3a16d15cb18a0d8115d8bd9f9e1ce3420310aea381eb42aa0a4f812cf047bb5441349257a05fba8a291515691e3cb51267279b2d2c3253f38471 languageName: node linkType: hard @@ -14183,12 +14162,10 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.23.2": - version: 0.23.2 - resolution: "scheduler@npm:0.23.2" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 +"scheduler@npm:^0.25.0": + version: 0.25.0 + resolution: "scheduler@npm:0.25.0" + checksum: 10c0/a4bb1da406b613ce72c1299db43759526058fdcc413999c3c3e0db8956df7633acf395cb20eb2303b6a65d658d66b6585d344460abaee8080b4aa931f10eaafe languageName: node linkType: hard