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
This commit is contained in:
parent
7fb85dc311
commit
fb9c23c500
14
package.json
14
package.json
@ -92,6 +92,7 @@
|
|||||||
"@agentic/exa": "^7.3.3",
|
"@agentic/exa": "^7.3.3",
|
||||||
"@agentic/searxng": "^7.3.3",
|
"@agentic/searxng": "^7.3.3",
|
||||||
"@agentic/tavily": "^7.3.3",
|
"@agentic/tavily": "^7.3.3",
|
||||||
|
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||||
"@anthropic-ai/sdk": "^0.38.0",
|
"@anthropic-ai/sdk": "^0.38.0",
|
||||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||||
"@electron-toolkit/eslint-config-ts": "^3.0.0",
|
"@electron-toolkit/eslint-config-ts": "^3.0.0",
|
||||||
@ -114,8 +115,8 @@
|
|||||||
"@types/md5": "^2.3.5",
|
"@types/md5": "^2.3.5",
|
||||||
"@types/node": "^18.19.9",
|
"@types/node": "^18.19.9",
|
||||||
"@types/pako": "^1.0.2",
|
"@types/pako": "^1.0.2",
|
||||||
"@types/react": "^18.2.48",
|
"@types/react": "^19.0.12",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@types/react-infinite-scroll-component": "^5.0.0",
|
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||||
"@types/tinycolor2": "^1",
|
"@types/tinycolor2": "^1",
|
||||||
"@vitejs/plugin-react": "^4.2.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",
|
"openai": "patch:openai@npm%3A4.77.3#~/.yarn/patches/openai-npm-4.77.3-59c6d42e7a.patch",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"react": "^18.2.0",
|
"rc-virtual-list": "^3.18.5",
|
||||||
"react-dom": "^18.2.0",
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
"react-hotkeys-hook": "^4.6.1",
|
"react-hotkeys-hook": "^4.6.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
"react-infinite-scroll-component": "^6.1.0",
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
@ -177,10 +179,6 @@
|
|||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"vite": "^5.0.12"
|
"vite": "^5.0.12"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^17.0.0 || ^18.0.0",
|
|
||||||
"react-dom": "^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch",
|
"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",
|
"@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import PaintingsPage from './pages/paintings/PaintingsPage'
|
|||||||
import SettingsPage from './pages/settings/SettingsPage'
|
import SettingsPage from './pages/settings/SettingsPage'
|
||||||
import TranslatePage from './pages/translate/TranslatePage'
|
import TranslatePage from './pages/translate/TranslatePage'
|
||||||
|
|
||||||
function App(): JSX.Element {
|
function App(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<StyleSheetManager>
|
<StyleSheetManager>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
OnDragStartResponder,
|
OnDragStartResponder,
|
||||||
ResponderProvided
|
ResponderProvided
|
||||||
} from '@hello-pangea/dnd'
|
} from '@hello-pangea/dnd'
|
||||||
|
import VirtualList from 'rc-virtual-list'
|
||||||
import { droppableReorder } from '@renderer/utils'
|
import { droppableReorder } from '@renderer/utils'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
|
||||||
@ -47,26 +48,28 @@ const DragableList: FC<Props<any>> = ({
|
|||||||
<Droppable droppableId="droppable" {...droppableProps}>
|
<Droppable droppableId="droppable" {...droppableProps}>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div {...provided.droppableProps} ref={provided.innerRef} style={style}>
|
<div {...provided.droppableProps} ref={provided.innerRef} style={style}>
|
||||||
{list.map((item, index) => {
|
<VirtualList data={list} itemKey="id">
|
||||||
const id = item.id || item
|
{(item, index) => {
|
||||||
return (
|
const id = item.id || item
|
||||||
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
|
return (
|
||||||
{(provided) => (
|
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
|
||||||
<div
|
{(provided) => (
|
||||||
ref={provided.innerRef}
|
<div
|
||||||
{...provided.draggableProps}
|
ref={provided.innerRef}
|
||||||
{...provided.dragHandleProps}
|
{...provided.draggableProps}
|
||||||
style={{
|
{...provided.dragHandleProps}
|
||||||
...listStyle,
|
style={{
|
||||||
...provided.draggableProps.style,
|
...listStyle,
|
||||||
marginBottom: 8
|
...provided.draggableProps.style,
|
||||||
}}>
|
marginBottom: 8
|
||||||
{children(item, index)}
|
}}>
|
||||||
</div>
|
{children(item, index)}
|
||||||
)}
|
</div>
|
||||||
</Draggable>
|
)}
|
||||||
)
|
</Draggable>
|
||||||
})}
|
)
|
||||||
|
}}
|
||||||
|
</VirtualList>
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor'
|
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'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
type Props = PropsWithChildren & JSX.IntrinsicElements['div']
|
type Props = PropsWithChildren & HTMLAttributes<HTMLDivElement>
|
||||||
|
|
||||||
export const Navbar: FC<Props> = ({ children, ...props }) => {
|
export const Navbar: FC<Props> = ({ children, ...props }) => {
|
||||||
const backgroundColor = useNavBackgroundColor()
|
const backgroundColor = useNavBackgroundColor()
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import isPropValid from '@emotion/is-prop-valid'
|
import isPropValid from '@emotion/is-prop-valid'
|
||||||
import { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { StyleSheetManager as StyledComponentsStyleSheetManager } from 'styled-components'
|
import { StyleSheetManager as StyledComponentsStyleSheetManager } from 'styled-components'
|
||||||
|
|
||||||
interface StyleSheetManagerProps {
|
interface StyleSheetManagerProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyleSheetManager = ({ children }: StyleSheetManagerProps): JSX.Element => {
|
const StyleSheetManager = ({ children }: StyleSheetManagerProps): React.ReactElement => {
|
||||||
return (
|
return (
|
||||||
<StyledComponentsStyleSheetManager
|
<StyledComponentsStyleSheetManager
|
||||||
shouldForwardProp={(prop, element) => {
|
shouldForwardProp={(prop, element) => {
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useMermaid } from '@renderer/hooks/useMermaid'
|
import { useMermaid } from '@renderer/hooks/useMermaid'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { CodeStyleVarious, ThemeMode } from '@renderer/types'
|
import { type CodeStyleVarious, ThemeMode } from '@renderer/types'
|
||||||
import React, { createContext, PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react'
|
import type React from 'react'
|
||||||
import {
|
import { createContext, type PropsWithChildren, use, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
BundledLanguage,
|
import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki'
|
||||||
bundledLanguages,
|
import { bundledLanguages, bundledThemes, createHighlighter } from 'shiki'
|
||||||
BundledTheme,
|
|
||||||
bundledThemes,
|
|
||||||
createHighlighter,
|
|
||||||
HighlighterGeneric
|
|
||||||
} from 'shiki'
|
|
||||||
|
|
||||||
interface SyntaxHighlighterContextType {
|
interface SyntaxHighlighterContextType {
|
||||||
codeToHtml: (code: string, language: string) => Promise<string>
|
codeToHtml: (code: string, language: string) => Promise<string>
|
||||||
@ -51,42 +46,47 @@ export const SyntaxHighlighterProvider: React.FC<PropsWithChildren> = ({ childre
|
|||||||
initHighlighter()
|
initHighlighter()
|
||||||
}, [highlighterTheme])
|
}, [highlighterTheme])
|
||||||
|
|
||||||
const codeToHtml = async (code: string, language: string) => {
|
const codeToHtml = useCallback(
|
||||||
if (!highlighter) return ''
|
async (_code: string, language: string) => {
|
||||||
|
{
|
||||||
|
if (!highlighter) return ''
|
||||||
|
|
||||||
const languageMap: Record<string, string> = {
|
const languageMap: Record<string, string> = {
|
||||||
vab: 'vb'
|
vab: 'vb'
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappedLanguage = languageMap[language] || language
|
const mappedLanguage = languageMap[language] || language
|
||||||
|
|
||||||
code = code?.trimEnd() ?? ''
|
const code = _code?.trimEnd() ?? ''
|
||||||
const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '<', '>': '>' })[char]!)
|
const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '<', '>': '>' })[char]!)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!highlighter.getLoadedLanguages().includes(mappedLanguage as BundledLanguage)) {
|
if (!highlighter.getLoadedLanguages().includes(mappedLanguage as BundledLanguage)) {
|
||||||
if (mappedLanguage in bundledLanguages || mappedLanguage === 'text') {
|
if (mappedLanguage in bundledLanguages || mappedLanguage === 'text') {
|
||||||
await highlighter.loadLanguage(mappedLanguage as BundledLanguage)
|
await highlighter.loadLanguage(mappedLanguage as BundledLanguage)
|
||||||
} else {
|
} else {
|
||||||
|
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return highlighter.codeToHtml(code, {
|
||||||
|
lang: mappedLanguage,
|
||||||
|
theme: highlighterTheme
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Error highlighting code for language '${mappedLanguage}':`, error)
|
||||||
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
|
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
return highlighter.codeToHtml(code, {
|
[highlighter, highlighterTheme]
|
||||||
lang: mappedLanguage,
|
)
|
||||||
theme: highlighterTheme
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Error highlighting code for language '${mappedLanguage}':`, error)
|
|
||||||
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <SyntaxHighlighterContext.Provider value={{ codeToHtml }}>{children}</SyntaxHighlighterContext.Provider>
|
return <SyntaxHighlighterContext.Provider value={{ codeToHtml }}>{children}</SyntaxHighlighterContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSyntaxHighlighter = () => {
|
export const useSyntaxHighlighter = () => {
|
||||||
const context = useContext(SyntaxHighlighterContext)
|
const context = use(SyntaxHighlighterContext)
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error('useSyntaxHighlighter must be used within a SyntaxHighlighterProvider')
|
throw new Error('useSyntaxHighlighter must be used within a SyntaxHighlighterProvider')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { ThemeMode } from '@renderer/types'
|
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 {
|
interface ThemeContextType {
|
||||||
theme: ThemeMode
|
theme: ThemeMode
|
||||||
@ -64,4 +64,4 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children, defaultT
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTheme = () => useContext(ThemeContext)
|
export const useTheme = () => use(ThemeContext)
|
||||||
|
|||||||
@ -5,14 +5,15 @@ import {
|
|||||||
clearStreamMessage,
|
clearStreamMessage,
|
||||||
clearTopicMessages,
|
clearTopicMessages,
|
||||||
commitStreamMessage,
|
commitStreamMessage,
|
||||||
|
deleteMessageAction,
|
||||||
resendMessage,
|
resendMessage,
|
||||||
selectDisplayCount,
|
selectDisplayCount,
|
||||||
selectTopicLoading,
|
selectTopicLoading,
|
||||||
selectTopicMessages,
|
selectTopicMessages,
|
||||||
setStreamMessage,
|
setStreamMessage,
|
||||||
setTopicLoading,
|
setTopicLoading,
|
||||||
updateMessage,
|
updateMessages,
|
||||||
updateMessages
|
updateMessageThunk
|
||||||
} from '@renderer/store/messages'
|
} from '@renderer/store/messages'
|
||||||
import type { Assistant, Message, Topic } from '@renderer/types'
|
import type { Assistant, Message, Topic } from '@renderer/types'
|
||||||
import { abortCompletion } from '@renderer/utils/abortController'
|
import { abortCompletion } from '@renderer/utils/abortController'
|
||||||
@ -27,17 +28,15 @@ import { TopicManager } from './useTopic'
|
|||||||
*/
|
*/
|
||||||
export function useMessageOperations(topic: Topic) {
|
export function useMessageOperations(topic: Topic) {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const messages = useAppSelector((state) => selectTopicMessages(state, topic.id))
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除单个消息
|
* 删除单个消息
|
||||||
*/
|
*/
|
||||||
const deleteMessage = useCallback(
|
const deleteMessage = useCallback(
|
||||||
async (message: Message) => {
|
async (id: string) => {
|
||||||
const newMessages = messages.filter((m) => m.id !== message.id)
|
await dispatch(deleteMessageAction(topic, id))
|
||||||
await dispatch(updateMessages(topic, newMessages))
|
|
||||||
},
|
},
|
||||||
[dispatch, topic, messages]
|
[dispatch, topic]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,10 +44,9 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
*/
|
*/
|
||||||
const deleteGroupMessages = useCallback(
|
const deleteGroupMessages = useCallback(
|
||||||
async (askId: string) => {
|
async (askId: string) => {
|
||||||
const newMessages = messages.filter((m) => m.askId !== askId)
|
await dispatch(deleteMessageAction(topic, askId, 'askId'))
|
||||||
await dispatch(updateMessages(topic, newMessages))
|
|
||||||
},
|
},
|
||||||
[dispatch, topic, messages]
|
[dispatch, topic]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,23 +56,17 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
async (messageId: string, updates: Partial<Message>) => {
|
async (messageId: string, updates: Partial<Message>) => {
|
||||||
// 如果更新包含内容变更,重新计算 token
|
// 如果更新包含内容变更,重新计算 token
|
||||||
if ('content' in updates) {
|
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) {
|
if (message) {
|
||||||
const updatedMessage = { ...message, ...updates }
|
const updatedMessage = { ...message, ...updates }
|
||||||
const usage = await estimateMessageUsage(updatedMessage)
|
const usage = await estimateMessageUsage(updatedMessage)
|
||||||
updates.usage = usage
|
updates.usage = usage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await dispatch(updateMessageThunk(topic.id, messageId, updates))
|
||||||
await dispatch(
|
|
||||||
updateMessage({
|
|
||||||
topicId: 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)
|
EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const loading = useAppSelector((state) => selectTopicLoading(state, topic.id))
|
|
||||||
const displayCount = useAppSelector(selectDisplayCount)
|
const displayCount = useAppSelector(selectDisplayCount)
|
||||||
// /**
|
// /**
|
||||||
// * 获取当前消息列表
|
// * 获取当前消息列表
|
||||||
@ -211,8 +202,6 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
|
||||||
loading,
|
|
||||||
displayCount,
|
displayCount,
|
||||||
updateMessages: updateMessagesAction,
|
updateMessages: updateMessagesAction,
|
||||||
deleteMessage,
|
deleteMessage,
|
||||||
@ -230,3 +219,13 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
resumeMessage
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
import './assets/styles/index.scss'
|
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 App from './App'
|
||||||
import MiniApp from './windows/mini/App'
|
import MiniApp from './windows/mini/App'
|
||||||
|
|
||||||
if (location.hash === '#/mini') {
|
if (location.hash === '#/mini') {
|
||||||
document.getElementById('spinner')?.remove()
|
document.getElementById('spinner')?.remove()
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<MiniApp />)
|
const root = createRoot(document.getElementById('root') as HTMLElement)
|
||||||
|
root.render(<MiniApp />)
|
||||||
} else {
|
} else {
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
|
const root = createRoot(document.getElementById('root') as HTMLElement)
|
||||||
|
root.render(<App />)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import TranslateButton from '@renderer/components/TranslateButton'
|
|||||||
import { isFunctionCallingModel, isGenerateImageModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
import { isFunctionCallingModel, isGenerateImageModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
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 { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings'
|
import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts'
|
import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts'
|
||||||
@ -83,10 +83,11 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
const containerRef = useRef(null)
|
const containerRef = useRef(null)
|
||||||
const { searching } = useRuntime()
|
const { searching } = useRuntime()
|
||||||
const { isBubbleStyle } = useMessageStyle()
|
const { isBubbleStyle } = useMessageStyle()
|
||||||
const { loading, pauseMessages } = useMessageOperations(topic)
|
const { pauseMessages } = useMessageOperations(topic)
|
||||||
|
const loading = useTopicLoading(topic)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const [spaceClickCount, setSpaceClickCount] = useState(0)
|
const [spaceClickCount, setSpaceClickCount] = useState(0)
|
||||||
const spaceClickTimer = useRef<NodeJS.Timeout>()
|
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||||
const [isTranslating, setIsTranslating] = useState(false)
|
const [isTranslating, setIsTranslating] = useState(false)
|
||||||
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
|
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
|
||||||
const [mentionModels, setMentionModels] = useState<Model[]>([])
|
const [mentionModels, setMentionModels] = useState<Model[]>([])
|
||||||
@ -96,7 +97,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
const [textareaHeight, setTextareaHeight] = useState<number>()
|
const [textareaHeight, setTextareaHeight] = useState<number>()
|
||||||
const startDragY = useRef<number>(0)
|
const startDragY = useRef<number>(0)
|
||||||
const startHeight = useRef<number>(0)
|
const startHeight = useRef<number>(0)
|
||||||
const currentMessageId = useRef<string>()
|
const currentMessageId = useRef<string>('')
|
||||||
const isVision = useMemo(() => isVisionModel(model), [model])
|
const isVision = useMemo(() => isVisionModel(model), [model])
|
||||||
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
|
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|||||||
@ -26,7 +26,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
|||||||
const match = /language-(\w+)/.exec(className || '') || children?.includes('\n')
|
const match = /language-(\w+)/.exec(className || '') || children?.includes('\n')
|
||||||
const { codeShowLineNumbers, fontSize, codeCollapsible, codeWrappable } = useSettings()
|
const { codeShowLineNumbers, fontSize, codeCollapsible, codeWrappable } = useSettings()
|
||||||
const language = match?.[1] ?? 'text'
|
const language = match?.[1] ?? 'text'
|
||||||
const [html, setHtml] = useState<string>('')
|
// const [html, setHtml] = useState<string>('')
|
||||||
const { codeToHtml } = useSyntaxHighlighter()
|
const { codeToHtml } = useSyntaxHighlighter()
|
||||||
const [isExpanded, setIsExpanded] = useState(!codeCollapsible)
|
const [isExpanded, setIsExpanded] = useState(!codeCollapsible)
|
||||||
const [isUnwrapped, setIsUnwrapped] = useState(!codeWrappable)
|
const [isUnwrapped, setIsUnwrapped] = useState(!codeWrappable)
|
||||||
@ -40,17 +40,14 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadHighlightedCode = async () => {
|
const loadHighlightedCode = async () => {
|
||||||
const highlightedHtml = await codeToHtml(children, language)
|
const highlightedHtml = await codeToHtml(children, language)
|
||||||
setHtml(highlightedHtml)
|
if (codeContentRef.current) {
|
||||||
|
codeContentRef.current.innerHTML = highlightedHtml
|
||||||
|
setShouldShowExpandButton(codeContentRef.current.scrollHeight > 350)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadHighlightedCode()
|
loadHighlightedCode()
|
||||||
}, [children, language, codeToHtml])
|
}, [children, language, codeToHtml])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (codeContentRef.current) {
|
|
||||||
setShouldShowExpandButton(codeContentRef.current.scrollHeight > 350)
|
|
||||||
}
|
|
||||||
}, [html])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!codeCollapsible) {
|
if (!codeCollapsible) {
|
||||||
setIsExpanded(true)
|
setIsExpanded(true)
|
||||||
@ -112,7 +109,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
|||||||
isShowLineNumbers={codeShowLineNumbers}
|
isShowLineNumbers={codeShowLineNumbers}
|
||||||
isUnwrapped={isUnwrapped}
|
isUnwrapped={isUnwrapped}
|
||||||
isCodeWrappable={codeWrappable}
|
isCodeWrappable={codeWrappable}
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
// dangerouslySetInnerHTML={{ __html: html }}
|
||||||
style={{
|
style={{
|
||||||
border: '0.5px solid var(--color-code-background)',
|
border: '0.5px solid var(--color-code-background)',
|
||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
|
|||||||
@ -50,7 +50,7 @@ const CustomNode: FC<{ data: any }> = ({ data }) => {
|
|||||||
let title = ''
|
let title = ''
|
||||||
let backgroundColor = 'var(--bg-color)'
|
let backgroundColor = 'var(--bg-color)'
|
||||||
let gradientColor = 'rgba(0, 0, 0, 0.03)'
|
let gradientColor = 'rgba(0, 0, 0, 0.03)'
|
||||||
let avatar: JSX.Element | null = null
|
let avatar: React.ReactNode | null = null
|
||||||
|
|
||||||
// 根据消息类型设置不同的样式和图标
|
// 根据消息类型设置不同的样式和图标
|
||||||
if (nodeType === 'user') {
|
if (nodeType === 'user') {
|
||||||
|
|||||||
@ -163,7 +163,7 @@ const MessageItem: FC<Props> = ({
|
|||||||
isLastMessage={isLastMessage}
|
isLastMessage={isLastMessage}
|
||||||
isAssistantMessage={isAssistantMessage}
|
isAssistantMessage={isAssistantMessage}
|
||||||
isGrouped={isGrouped}
|
isGrouped={isGrouped}
|
||||||
messageContainerRef={messageContainerRef}
|
messageContainerRef={messageContainerRef as React.RefObject<HTMLDivElement>}
|
||||||
setModel={setModel}
|
setModel={setModel}
|
||||||
/>
|
/>
|
||||||
</MessageFooter>
|
</MessageFooter>
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import { useSettings } from '@renderer/hooks/useSettings'
|
|||||||
import { getMessageModelId } from '@renderer/services/MessagesService'
|
import { getMessageModelId } from '@renderer/services/MessagesService'
|
||||||
import { getModelName } from '@renderer/services/ModelService'
|
import { getModelName } from '@renderer/services/ModelService'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { updateMessage } from '@renderer/store/messages'
|
import { updateMessageThunk } from '@renderer/store/messages'
|
||||||
import { Message } from '@renderer/types'
|
import type { Message } from '@renderer/types'
|
||||||
import { isEmoji, removeLeadingEmoji } from '@renderer/utils'
|
import { isEmoji, removeLeadingEmoji } from '@renderer/utils'
|
||||||
import { Avatar } from 'antd'
|
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 { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
interface MessageLineProps {
|
interface MessageLineProps {
|
||||||
@ -100,15 +100,9 @@ const MessageAnchorLine: FC<MessageLineProps> = ({ messages }) => {
|
|||||||
(message: Message) => {
|
(message: Message) => {
|
||||||
const groupMessages = messages.filter((m) => m.askId === message.askId)
|
const groupMessages = messages.filter((m) => m.askId === message.askId)
|
||||||
if (groupMessages.length > 1) {
|
if (groupMessages.length > 1) {
|
||||||
groupMessages.forEach((m) => {
|
for (const m of groupMessages) {
|
||||||
dispatch(
|
dispatch(updateMessageThunk(m.topicId, m.id, { foldSelected: m.id === message.id }))
|
||||||
updateMessage({
|
}
|
||||||
topicId: m.topicId,
|
|
||||||
messageId: m.id,
|
|
||||||
updates: { foldSelected: m.id === message.id }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const messageElement = document.getElementById(`message-${message.id}`)
|
const messageElement = document.getElementById(`message-${message.id}`)
|
||||||
|
|||||||
@ -17,12 +17,12 @@ import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
|||||||
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
|
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
|
||||||
import { isReasoningModel } from '@renderer/config/models'
|
import { isReasoningModel } from '@renderer/config/models'
|
||||||
import { TranslateLanguageOptions } from '@renderer/config/translate'
|
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 { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { getMessageTitle, resetAssistantMessage } from '@renderer/services/MessagesService'
|
import { getMessageTitle, resetAssistantMessage } from '@renderer/services/MessagesService'
|
||||||
import { translateText } from '@renderer/services/TranslateService'
|
import { translateText } from '@renderer/services/TranslateService'
|
||||||
import { Message, Model } from '@renderer/types'
|
import type { Message, Model } from '@renderer/types'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import type { Assistant, Topic } from '@renderer/types'
|
||||||
import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, removeTrailingDoubleSpaces } from '@renderer/utils'
|
import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, removeTrailingDoubleSpaces } from '@renderer/utils'
|
||||||
import {
|
import {
|
||||||
exportMarkdownToJoplin,
|
exportMarkdownToJoplin,
|
||||||
@ -62,15 +62,9 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
const [showRegenerateTooltip, setShowRegenerateTooltip] = useState(false)
|
const [showRegenerateTooltip, setShowRegenerateTooltip] = useState(false)
|
||||||
const [showDeleteTooltip, setShowDeleteTooltip] = useState(false)
|
const [showDeleteTooltip, setShowDeleteTooltip] = useState(false)
|
||||||
const assistantModel = assistant?.model
|
const assistantModel = assistant?.model
|
||||||
const {
|
const { editMessage, setStreamMessage, deleteMessage, resendMessage, commitStreamMessage, clearStreamMessage } =
|
||||||
loading,
|
useMessageOperations(topic)
|
||||||
editMessage,
|
const loading = useTopicLoading(topic)
|
||||||
setStreamMessage,
|
|
||||||
deleteMessage,
|
|
||||||
resendMessage,
|
|
||||||
commitStreamMessage,
|
|
||||||
clearStreamMessage
|
|
||||||
} = useMessageOperations(topic)
|
|
||||||
|
|
||||||
const isUserMessage = message.role === 'user'
|
const isUserMessage = message.role === 'user'
|
||||||
|
|
||||||
@ -382,7 +376,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
okButtonProps={{ danger: true }}
|
okButtonProps={{ danger: true }}
|
||||||
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
||||||
onOpenChange={(open) => open && setShowDeleteTooltip(false)}
|
onOpenChange={(open) => open && setShowDeleteTooltip(false)}
|
||||||
onConfirm={() => deleteMessage(message)}>
|
onConfirm={() => deleteMessage(message.id)}>
|
||||||
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
|
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={t('common.delete')}
|
title={t('common.delete')}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { CheckOutlined, ExpandOutlined, LoadingOutlined } from '@ant-design/icons'
|
import { CheckOutlined, ExpandOutlined, LoadingOutlined } from '@ant-design/icons'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { MCPToolResponse, Message } from '@renderer/types'
|
import { Message } from '@renderer/types'
|
||||||
import { Collapse, message as antdMessage, Modal, Tooltip } from 'antd'
|
import { Collapse, message as antdMessage, Modal, Tooltip } from 'antd'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import { FC, useMemo, useState } from 'react'
|
import { FC, useMemo, useState } from 'react'
|
||||||
@ -42,9 +42,9 @@ const MessageTools: FC<Props> = ({ message }) => {
|
|||||||
|
|
||||||
// Format tool responses for collapse items
|
// Format tool responses for collapse items
|
||||||
const getCollapseItems = () => {
|
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
|
// Add tool responses
|
||||||
toolResponses.forEach((toolResponse: MCPToolResponse) => {
|
for (const toolResponse of toolResponses) {
|
||||||
const { id, tool, status, response } = toolResponse
|
const { id, tool, status, response } = toolResponse
|
||||||
const isInvoking = status === 'invoking'
|
const isInvoking = status === 'invoking'
|
||||||
const isDone = status === 'done'
|
const isDone = status === 'done'
|
||||||
@ -105,7 +105,7 @@ const MessageTools: FC<Props> = ({ message }) => {
|
|||||||
</ToolResponseContainer>
|
</ToolResponseContainer>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
@ -129,7 +129,9 @@ const MessageTools: FC<Props> = ({ message }) => {
|
|||||||
onCancel={() => setExpandedResponse(null)}
|
onCancel={() => setExpandedResponse(null)}
|
||||||
footer={null}
|
footer={null}
|
||||||
width="80%"
|
width="80%"
|
||||||
bodyStyle={{ maxHeight: '80vh', overflow: 'auto' }}>
|
styles={{
|
||||||
|
body: { maxHeight: '80vh', overflow: 'auto' }
|
||||||
|
}}>
|
||||||
{expandedResponse && (
|
{expandedResponse && (
|
||||||
<ExpandedResponseContainer style={{ fontFamily, fontSize }}>
|
<ExpandedResponseContainer style={{ fontFamily, fontSize }}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
|
|||||||
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
|
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations, useTopicLoading, useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||||
import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic'
|
import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic'
|
||||||
@ -38,6 +38,42 @@ interface MessagesProps {
|
|||||||
setActiveTopic: (topic: Topic) => void
|
setActiveTopic: (topic: Topic) => 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<MessagesProps> = ({ assistant, topic, setActiveTopic }) => {
|
const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { showTopics, topicPosition, showAssistants, messageNavigation } = useSettings()
|
const { showTopics, topicPosition, showAssistants, messageNavigation } = useSettings()
|
||||||
@ -48,9 +84,9 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
|
|||||||
const [hasMore, setHasMore] = useState(false)
|
const [hasMore, setHasMore] = useState(false)
|
||||||
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
||||||
const [isProcessingContext, setIsProcessingContext] = useState(false)
|
const [isProcessingContext, setIsProcessingContext] = useState(false)
|
||||||
const { messages, displayCount, loading, updateMessages, clearTopicMessages, deleteMessage } =
|
const messages = useTopicMessages(topic)
|
||||||
useMessageOperations(topic)
|
const { displayCount, updateMessages, clearTopicMessages, deleteMessage } = useMessageOperations(topic)
|
||||||
|
const loading = useTopicLoading(topic)
|
||||||
const messagesRef = useRef<Message[]>(messages)
|
const messagesRef = useRef<Message[]>(messages)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -58,9 +94,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
|
|||||||
}, [messages])
|
}, [messages])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const reversedMessages = [...messages].reverse()
|
const newDisplayMessages = computeDisplayMessages(messages, 0, displayCount)
|
||||||
const newDisplayMessages = reversedMessages.slice(0, displayCount)
|
|
||||||
|
|
||||||
setDisplayMessages(newDisplayMessages)
|
setDisplayMessages(newDisplayMessages)
|
||||||
setHasMore(messages.length > displayCount)
|
setHasMore(messages.length > displayCount)
|
||||||
}, [messages, displayCount])
|
}, [messages, displayCount])
|
||||||
@ -73,7 +107,15 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
|
|||||||
}, [showAssistants, showTopics, topicPosition])
|
}, [showAssistants, showTopics, topicPosition])
|
||||||
|
|
||||||
const scrollToBottom = useCallback(() => {
|
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(() => {
|
useEffect(() => {
|
||||||
@ -122,7 +164,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
|
|||||||
const lastMessage = last(messages)
|
const lastMessage = last(messages)
|
||||||
|
|
||||||
if (lastMessage?.type === 'clear') {
|
if (lastMessage?.type === 'clear') {
|
||||||
await deleteMessage(lastMessage)
|
await deleteMessage(lastMessage.id)
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -183,10 +225,9 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
|
|||||||
setIsLoadingMore(true)
|
setIsLoadingMore(true)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const currentLength = displayMessages.length
|
const currentLength = displayMessages.length
|
||||||
const reversedMessages = [...messages].reverse()
|
const newMessages = computeDisplayMessages(messages, currentLength, LOAD_MORE_COUNT)
|
||||||
const moreMessages = reversedMessages.slice(currentLength, currentLength + LOAD_MORE_COUNT)
|
|
||||||
|
|
||||||
setDisplayMessages((prev) => [...prev, ...moreMessages])
|
setDisplayMessages((prev) => [...prev, ...newMessages])
|
||||||
setHasMore(currentLength + LOAD_MORE_COUNT < messages.length)
|
setHasMore(currentLength + LOAD_MORE_COUNT < messages.length)
|
||||||
setIsLoadingMore(false)
|
setIsLoadingMore(false)
|
||||||
}, 300)
|
}, 300)
|
||||||
@ -199,7 +240,6 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
|
|||||||
window.message.success(t('message.copy.success'))
|
window.message.success(t('message.copy.success'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
id="messages"
|
id="messages"
|
||||||
@ -214,8 +254,8 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
|
|||||||
next={loadMoreMessages}
|
next={loadMoreMessages}
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
loader={null}
|
loader={null}
|
||||||
inverse={true}
|
scrollableTarget="messages"
|
||||||
scrollableTarget="messages">
|
inverse>
|
||||||
<ScrollContainer>
|
<ScrollContainer>
|
||||||
<LoaderContainer $loading={isLoadingMore}>
|
<LoaderContainer $loading={isLoadingMore}>
|
||||||
<BeatLoader size={8} color="var(--color-text-2)" />
|
<BeatLoader size={8} color="var(--color-text-2)" />
|
||||||
|
|||||||
@ -56,7 +56,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
|||||||
const borderRadius = showTopicTime ? 12 : 'var(--list-item-border-radius)'
|
const borderRadius = showTopicTime ? 12 : 'var(--list-item-border-radius)'
|
||||||
|
|
||||||
const [deletingTopicId, setDeletingTopicId] = useState<string | null>(null)
|
const [deletingTopicId, setDeletingTopicId] = useState<string | null>(null)
|
||||||
const deleteTimerRef = useRef<NodeJS.Timeout>()
|
const deleteTimerRef = useRef<NodeJS.Timeout>(null)
|
||||||
|
|
||||||
const pendingTopics = useMemo(() => {
|
const pendingTopics = useMemo(() => {
|
||||||
return new Set<string>()
|
return new Set<string>()
|
||||||
|
|||||||
@ -23,11 +23,12 @@ import { translateText } from '@renderer/services/TranslateService'
|
|||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { DEFAULT_PAINTING } from '@renderer/store/paintings'
|
import { DEFAULT_PAINTING } from '@renderer/store/paintings'
|
||||||
import { setGenerating } from '@renderer/store/runtime'
|
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 { getErrorMessage } from '@renderer/utils'
|
||||||
import { Button, Input, InputNumber, Radio, Select, Slider, Switch, Tooltip } from 'antd'
|
import { Button, Input, InputNumber, Radio, Select, Slider, Switch, Tooltip } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
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 { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@ -243,7 +244,7 @@ const PaintingsPage: FC = () => {
|
|||||||
const { autoTranslateWithSpace } = useSettings()
|
const { autoTranslateWithSpace } = useSettings()
|
||||||
const [spaceClickCount, setSpaceClickCount] = useState(0)
|
const [spaceClickCount, setSpaceClickCount] = useState(0)
|
||||||
const [isTranslating, setIsTranslating] = useState(false)
|
const [isTranslating, setIsTranslating] = useState(false)
|
||||||
const spaceClickTimer = useRef<NodeJS.Timeout>()
|
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||||
|
|
||||||
const translate = async () => {
|
const translate = async () => {
|
||||||
if (isTranslating) {
|
if (isTranslating) {
|
||||||
|
|||||||
@ -152,7 +152,12 @@ const PopupContainer: React.FC<Props> = ({ server, create, resolve }) => {
|
|||||||
width={600}
|
width={600}
|
||||||
transitionName="ant-move-down"
|
transitionName="ant-move-down"
|
||||||
centered
|
centered
|
||||||
bodyStyle={{ maxHeight: '70vh', overflowY: 'auto' }}>
|
styles={{
|
||||||
|
body: {
|
||||||
|
maxHeight: '70vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
}
|
||||||
|
}}>
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout="vertical">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="name"
|
name="name"
|
||||||
|
|||||||
@ -280,7 +280,11 @@ const ShortcutSettings: FC = () => {
|
|||||||
<HStack alignItems="center" style={{ position: 'relative' }}>
|
<HStack alignItems="center" style={{ position: 'relative' }}>
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<ShortcutInput
|
<ShortcutInput
|
||||||
ref={(el) => el && (inputRefs.current[record.key] = el)}
|
ref={(el) => {
|
||||||
|
if (el) {
|
||||||
|
inputRefs.current[record.key] = el
|
||||||
|
}
|
||||||
|
}}
|
||||||
value={formatShortcut(shortcut)}
|
value={formatShortcut(shortcut)}
|
||||||
placeholder={t('settings.shortcuts.press_shortcut')}
|
placeholder={t('settings.shortcuts.press_shortcut')}
|
||||||
onKeyDown={(e) => handleKeyDown(e, record)}
|
onKeyDown={(e) => handleKeyDown(e, record)}
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import { fetchChatCompletion } from '@renderer/services/ApiService'
|
|||||||
import { getAssistantMessage, resetAssistantMessage } from '@renderer/services/MessagesService'
|
import { getAssistantMessage, resetAssistantMessage } from '@renderer/services/MessagesService'
|
||||||
import type { AppDispatch, RootState } from '@renderer/store'
|
import type { AppDispatch, RootState } from '@renderer/store'
|
||||||
import type { Assistant, Message, Topic } from '@renderer/types'
|
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 { clearTopicQueue, getTopicQueue, waitForTopicQueue } from '@renderer/utils/queue'
|
||||||
import { cloneDeep, isEmpty, throttle } from 'lodash'
|
import { isEmpty, throttle } from 'lodash'
|
||||||
|
|
||||||
export interface MessagesState {
|
export interface MessagesState {
|
||||||
messagesByTopic: Record<string, Message[]>
|
messagesByTopic: Record<string, Message[]>
|
||||||
@ -113,14 +113,10 @@ const messagesSlice = createSlice({
|
|||||||
) => {
|
) => {
|
||||||
const { topicId, messageId, updates } = action.payload
|
const { topicId, messageId, updates } = action.payload
|
||||||
const topicMessages = state.messagesByTopic[topicId]
|
const topicMessages = state.messagesByTopic[topicId]
|
||||||
|
|
||||||
if (topicMessages) {
|
if (topicMessages) {
|
||||||
const message = topicMessages.find((msg) => msg.id === messageId)
|
const message = topicMessages.find((msg) => msg.id === messageId)
|
||||||
if (message) {
|
if (message) {
|
||||||
Object.assign(message, updates)
|
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 isGroupedMessage = messageToReset.length > 1
|
||||||
const resetMessage = resetAssistantMessage(m, isGroupedMessage ? m.model : assistant.model)
|
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
|
return resetMessage
|
||||||
})
|
})
|
||||||
@ -263,7 +259,7 @@ export const sendMessage =
|
|||||||
const { model, id } = messageToReset
|
const { model, id } = messageToReset
|
||||||
const resetMessage = resetAssistantMessage(messageToReset, model)
|
const resetMessage = resetAssistantMessage(messageToReset, model)
|
||||||
// 更新状态
|
// 更新状态
|
||||||
dispatch(updateMessage({ topicId: topic.id, messageId: id, updates: resetMessage }))
|
dispatch(updateMessageThunk(topic.id, id, resetMessage))
|
||||||
// 使用重置后的消息
|
// 使用重置后的消息
|
||||||
assistantMessages.push(resetMessage)
|
assistantMessages.push(resetMessage)
|
||||||
}
|
}
|
||||||
@ -396,10 +392,9 @@ export const sendMessage =
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error in chat completion:', error)
|
console.error('Error in chat completion:', error)
|
||||||
dispatch(
|
dispatch(
|
||||||
updateMessage({
|
updateMessageThunk(topic.id, assistantMessage.id, {
|
||||||
topicId: topic.id,
|
status: 'error',
|
||||||
messageId: assistantMessage.id,
|
error: { message: error.message }
|
||||||
updates: { status: 'error', error: { message: error.message } }
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
dispatch(clearStreamMessage({ topicId: topic.id, messageId: assistantMessage.id }))
|
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')
|
const userMessage = topicMessages.find((m) => m.id === message.askId && m.role === 'user')
|
||||||
if (!userMessage) {
|
if (!userMessage) {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateMessage({
|
updateMessageThunk(topic.id, message.id, {
|
||||||
topicId: topic.id,
|
status: 'error',
|
||||||
messageId: message.id,
|
error: { message: i18n.t('error.user_message_not_found') }
|
||||||
updates: { status: 'error', error: { message: i18n.t('error.user_message_not_found') } }
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
console.error(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,同时更新缓存
|
// 修改的 updateMessages thunk,同时更新缓存
|
||||||
export const updateMessages = (topic: Topic, messages: Message[]) => async (dispatch: AppDispatch) => {
|
export const updateMessages = (topic: Topic, messages: Message[]) => async (dispatch: AppDispatch) => {
|
||||||
try {
|
try {
|
||||||
@ -534,6 +536,28 @@ export const updateMessages = (topic: Topic, messages: Message[]) => async (disp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增一个 thunk 来处理消息更新
|
||||||
|
export const updateMessageThunk =
|
||||||
|
(topicId: string, messageId: string, updates: Partial<Message>) =>
|
||||||
|
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
|
// Selectors
|
||||||
export const selectCurrentTopicId = (state: RootState): string | null => {
|
export const selectCurrentTopicId = (state: RootState): string | null => {
|
||||||
const messagesState = state.messages
|
const messagesState = state.messages
|
||||||
@ -546,11 +570,10 @@ export const selectTopicMessages = createSelector(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 获取特定话题的loading状态
|
// 获取特定话题的loading状态
|
||||||
export const selectTopicLoading = (state: RootState, topicId?: string): boolean => {
|
export const selectTopicLoading = createSelector(
|
||||||
const messagesState = state.messages as MessagesState
|
[(state: RootState) => state.messages.loadingByTopic, (_, topicId?: string) => topicId],
|
||||||
const currentTopicId = topicId || messagesState.currentTopic?.id || ''
|
(loadingByTopic, topicId) => (topicId ? (loadingByTopic[topicId] ?? false) : false)
|
||||||
return currentTopicId ? (messagesState.loadingByTopic[currentTopicId] ?? false) : false
|
)
|
||||||
}
|
|
||||||
|
|
||||||
export const selectDisplayCount = (state: RootState): number => {
|
export const selectDisplayCount = (state: RootState): number => {
|
||||||
const messagesState = state.messages as MessagesState
|
const messagesState = state.messages as MessagesState
|
||||||
|
|||||||
@ -306,7 +306,7 @@ export async function captureDiv(divRef: React.RefObject<HTMLDivElement>) {
|
|||||||
return Promise.resolve(undefined)
|
return Promise.resolve(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const captureScrollableDiv = async (divRef: React.RefObject<HTMLDivElement>) => {
|
export const captureScrollableDiv = async (divRef: React.RefObject<HTMLDivElement | null>) => {
|
||||||
if (divRef.current) {
|
if (divRef.current) {
|
||||||
try {
|
try {
|
||||||
const div = divRef.current
|
const div = divRef.current
|
||||||
@ -392,7 +392,7 @@ export const captureScrollableDiv = async (divRef: React.RefObject<HTMLDivElemen
|
|||||||
return Promise.resolve(undefined)
|
return Promise.resolve(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const captureScrollableDivAsDataURL = async (divRef: React.RefObject<HTMLDivElement>) => {
|
export const captureScrollableDivAsDataURL = async (divRef: React.RefObject<HTMLDivElement | null>) => {
|
||||||
return captureScrollableDiv(divRef).then((canvas) => {
|
return captureScrollableDiv(divRef).then((canvas) => {
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
return canvas.toDataURL('image/png')
|
return canvas.toDataURL('image/png')
|
||||||
@ -401,7 +401,10 @@ export const captureScrollableDivAsDataURL = async (divRef: React.RefObject<HTML
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const captureScrollableDivAsBlob = async (divRef: React.RefObject<HTMLDivElement>, func: BlobCallback) => {
|
export const captureScrollableDivAsBlob = async (
|
||||||
|
divRef: React.RefObject<HTMLDivElement | null>,
|
||||||
|
func: BlobCallback
|
||||||
|
) => {
|
||||||
await captureScrollableDiv(divRef).then((canvas) => {
|
await captureScrollableDiv(divRef).then((canvas) => {
|
||||||
canvas?.toBlob(func, 'image/png')
|
canvas?.toBlob(func, 'image/png')
|
||||||
})
|
})
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { SyntaxHighlighterProvider } from '../../context/SyntaxHighlighterProvid
|
|||||||
import { ThemeProvider } from '../../context/ThemeProvider'
|
import { ThemeProvider } from '../../context/ThemeProvider'
|
||||||
import HomeWindow from './home/HomeWindow'
|
import HomeWindow from './home/HomeWindow'
|
||||||
|
|
||||||
function MiniWindow(): JSX.Element {
|
function MiniWindow(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
|||||||
101
yarn.lock
101
yarn.lock
@ -165,6 +165,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@anthropic-ai/sdk@npm:^0.38.0":
|
||||||
version: 0.38.0
|
version: 0.38.0
|
||||||
resolution: "@anthropic-ai/sdk@npm:0.38.0"
|
resolution: "@anthropic-ai/sdk@npm:0.38.0"
|
||||||
@ -3455,19 +3466,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/prop-types@npm:*":
|
"@types/react-dom@npm:^19.0.4":
|
||||||
version: 15.7.14
|
version: 19.0.4
|
||||||
resolution: "@types/prop-types@npm:15.7.14"
|
resolution: "@types/react-dom@npm:19.0.4"
|
||||||
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"
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@types/react": ^18.0.0
|
"@types/react": ^19.0.0
|
||||||
checksum: 10c0/b163d35a6b32a79f5782574a7aeb12a31a647e248792bf437e6d596e2676961c394c5e3c6e91d1ce44ae90441dbaf93158efb4f051c0d61e2612f1cb04ce4faa
|
checksum: 10c0/4e71853919b94df9e746a4bd73f8180e9ae13016333ce9c543dcba9f4f4c8fe6e28b038ca6ee61c24e291af8e03ca3bc5ded17c46dee938fcb32d71186fda7a3
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -3480,7 +3484,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react@npm:*":
|
"@types/react@npm:*, @types/react@npm:^19.0.12":
|
||||||
version: 19.0.12
|
version: 19.0.12
|
||||||
resolution: "@types/react@npm:19.0.12"
|
resolution: "@types/react@npm:19.0.12"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3489,16 +3493,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@types/responselike@npm:^1.0.0":
|
||||||
version: 1.0.3
|
version: 1.0.3
|
||||||
resolution: "@types/responselike@npm:1.0.3"
|
resolution: "@types/responselike@npm:1.0.3"
|
||||||
@ -3766,6 +3760,7 @@ __metadata:
|
|||||||
"@agentic/exa": "npm:^7.3.3"
|
"@agentic/exa": "npm:^7.3.3"
|
||||||
"@agentic/searxng": "npm:^7.3.3"
|
"@agentic/searxng": "npm:^7.3.3"
|
||||||
"@agentic/tavily": "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"
|
"@anthropic-ai/sdk": "npm:^0.38.0"
|
||||||
"@cherrystudio/embedjs": "npm:^0.1.28"
|
"@cherrystudio/embedjs": "npm:^0.1.28"
|
||||||
"@cherrystudio/embedjs-libsql": "npm:^0.1.28"
|
"@cherrystudio/embedjs-libsql": "npm:^0.1.28"
|
||||||
@ -3804,8 +3799,8 @@ __metadata:
|
|||||||
"@types/md5": "npm:^2.3.5"
|
"@types/md5": "npm:^2.3.5"
|
||||||
"@types/node": "npm:^18.19.9"
|
"@types/node": "npm:^18.19.9"
|
||||||
"@types/pako": "npm:^1.0.2"
|
"@types/pako": "npm:^1.0.2"
|
||||||
"@types/react": "npm:^18.2.48"
|
"@types/react": "npm:^19.0.12"
|
||||||
"@types/react-dom": "npm:^18.2.18"
|
"@types/react-dom": "npm:^19.0.4"
|
||||||
"@types/react-infinite-scroll-component": "npm:^5.0.0"
|
"@types/react-infinite-scroll-component": "npm:^5.0.0"
|
||||||
"@types/tinycolor2": "npm:^1"
|
"@types/tinycolor2": "npm:^1"
|
||||||
"@vitejs/plugin-react": "npm:^4.2.1"
|
"@vitejs/plugin-react": "npm:^4.2.1"
|
||||||
@ -3853,8 +3848,9 @@ __metadata:
|
|||||||
p-queue: "npm:^8.1.0"
|
p-queue: "npm:^8.1.0"
|
||||||
prettier: "npm:^3.5.3"
|
prettier: "npm:^3.5.3"
|
||||||
proxy-agent: "npm:^6.5.0"
|
proxy-agent: "npm:^6.5.0"
|
||||||
react: "npm:^18.2.0"
|
rc-virtual-list: "npm:^3.18.5"
|
||||||
react-dom: "npm:^18.2.0"
|
react: "npm:^19.0.0"
|
||||||
|
react-dom: "npm:^19.0.0"
|
||||||
react-hotkeys-hook: "npm:^4.6.1"
|
react-hotkeys-hook: "npm:^4.6.1"
|
||||||
react-i18next: "npm:^14.1.2"
|
react-i18next: "npm:^14.1.2"
|
||||||
react-infinite-scroll-component: "npm:^6.1.0"
|
react-infinite-scroll-component: "npm:^6.1.0"
|
||||||
@ -3884,9 +3880,6 @@ __metadata:
|
|||||||
vite: "npm:^5.0.12"
|
vite: "npm:^5.0.12"
|
||||||
webdav: "npm:^5.8.0"
|
webdav: "npm:^5.8.0"
|
||||||
zipread: "npm:^1.3.3"
|
zipread: "npm:^1.3.3"
|
||||||
peerDependencies:
|
|
||||||
react: ^17.0.0 || ^18.0.0
|
|
||||||
react-dom: ^17.0.0 || ^18.0.0
|
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
@ -9190,7 +9183,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 4.0.0
|
||||||
resolution: "js-tokens@npm:4.0.0"
|
resolution: "js-tokens@npm:4.0.0"
|
||||||
checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed
|
checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed
|
||||||
@ -9798,17 +9791,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"lop@npm:^0.4.1":
|
||||||
version: 0.4.2
|
version: 0.4.2
|
||||||
resolution: "lop@npm:0.4.2"
|
resolution: "lop@npm:0.4.2"
|
||||||
@ -13272,7 +13254,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 3.18.5
|
||||||
resolution: "rc-virtual-list@npm:3.18.5"
|
resolution: "rc-virtual-list@npm:3.18.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -13301,15 +13283,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-dom@npm:^18.2.0":
|
"react-dom@npm:^19.0.0":
|
||||||
version: 18.3.1
|
version: 19.0.0
|
||||||
resolution: "react-dom@npm:18.3.1"
|
resolution: "react-dom@npm:19.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: "npm:^1.1.0"
|
scheduler: "npm:^0.25.0"
|
||||||
scheduler: "npm:^0.23.2"
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.3.1
|
react: ^19.0.0
|
||||||
checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85
|
checksum: 10c0/a36ce7ab507b237ae2759c984cdaad4af4096d8199fb65b3815c16825e5cfeb7293da790a3fc2184b52bfba7ba3ff31c058c01947aff6fd1a3701632aabaa6a9
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -13480,12 +13461,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react@npm:^18.2.0":
|
"react@npm:^19.0.0":
|
||||||
version: 18.3.1
|
version: 19.0.0
|
||||||
resolution: "react@npm:18.3.1"
|
resolution: "react@npm:19.0.0"
|
||||||
dependencies:
|
checksum: 10c0/9cad8f103e8e3a16d15cb18a0d8115d8bd9f9e1ce3420310aea381eb42aa0a4f812cf047bb5441349257a05fba8a291515691e3cb51267279b2d2c3253f38471
|
||||||
loose-envify: "npm:^1.1.0"
|
|
||||||
checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -14183,12 +14162,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"scheduler@npm:^0.23.2":
|
"scheduler@npm:^0.25.0":
|
||||||
version: 0.23.2
|
version: 0.25.0
|
||||||
resolution: "scheduler@npm:0.23.2"
|
resolution: "scheduler@npm:0.25.0"
|
||||||
dependencies:
|
checksum: 10c0/a4bb1da406b613ce72c1299db43759526058fdcc413999c3c3e0db8956df7633acf395cb20eb2303b6a65d658d66b6585d344460abaee8080b4aa931f10eaafe
|
||||||
loose-envify: "npm:^1.1.0"
|
|
||||||
checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user