diff --git a/src/renderer/src/databases/upgrades.ts b/src/renderer/src/databases/upgrades.ts index b9449b1bfd..8f14965f53 100644 --- a/src/renderer/src/databases/upgrades.ts +++ b/src/renderer/src/databases/upgrades.ts @@ -110,6 +110,37 @@ export async function upgradeToV7(tx: Transaction): Promise { const citationDataToCreate: Partial> = {} let hasCitationData = false + // 2. Thinking Block (Status is SUCCESS) + // 挪到前面,尽量保持与旧版本的一致性 + if (oldMessage.reasoning_content?.trim()) { + const block = createThinkingBlock(oldMessage.id, oldMessage.reasoning_content, { + createdAt: oldMessage.createdAt, + status: MessageBlockStatus.SUCCESS // Thinking block is complete content + }) + blocksToCreate.push(block) + messageBlockIds.push(block.id) + } + + // 7. Tool Blocks (Status based on original mcpTool status) + // 挪到前面,尽量保持与旧版本的一致性 + if (oldMessage.metadata?.mcpTools?.length) { + oldMessage.metadata.mcpTools.forEach((mcpTool) => { + const block = createToolBlock(oldMessage.id, mcpTool.id, { + // Determine status based on original tool status + status: MessageBlockStatus.SUCCESS, + content: mcpTool.response, + error: + mcpTool.status !== 'done' + ? { message: 'MCP Tool did not complete', originalStatus: mcpTool.status } + : undefined, + createdAt: oldMessage.createdAt, + metadata: { rawMcpToolResponse: mcpTool } + }) + blocksToCreate.push(block) + messageBlockIds.push(block.id) + }) + } + // 1. Main Text Block if (oldMessage.content?.trim()) { const block = createMainTextBlock(oldMessage.id, oldMessage.content, { @@ -121,16 +152,6 @@ export async function upgradeToV7(tx: Transaction): Promise { messageBlockIds.push(block.id) } - // 2. Thinking Block (Status is SUCCESS) - if (oldMessage.reasoning_content?.trim()) { - const block = createThinkingBlock(oldMessage.id, oldMessage.reasoning_content, { - createdAt: oldMessage.createdAt, - status: MessageBlockStatus.SUCCESS // Thinking block is complete content - }) - blocksToCreate.push(block) - messageBlockIds.push(block.id) - } - // 3. Translation Block (Status is SUCCESS) if (oldMessage.translatedContent?.trim()) { const block = createTranslationBlock(oldMessage.id, oldMessage.translatedContent, 'unknown', { @@ -177,25 +198,6 @@ export async function upgradeToV7(tx: Transaction): Promise { // 6. Web Search Block - REMOVED, data moved to citation collection // if (oldMessage.metadata?.webSearch?.results?.length) { ... } - // 7. Tool Blocks (Status based on original mcpTool status) - if (oldMessage.metadata?.mcpTools?.length) { - oldMessage.metadata.mcpTools.forEach((mcpTool) => { - const block = createToolBlock(oldMessage.id, mcpTool.id, { - // Determine status based on original tool status - status: MessageBlockStatus.SUCCESS, - content: mcpTool.response, - error: - mcpTool.status !== 'done' - ? { message: 'MCP Tool did not complete', originalStatus: mcpTool.status } - : undefined, - createdAt: oldMessage.createdAt, - metadata: { rawMcpToolResponse: mcpTool } - }) - blocksToCreate.push(block) - messageBlockIds.push(block.id) - }) - } - // 8. Collect Citation and Reference Data (Simplified: Independent checks) if (oldMessage.metadata?.groundingMetadata) { hasCitationData = true diff --git a/src/renderer/src/hooks/useAssistant.ts b/src/renderer/src/hooks/useAssistant.ts index b99074c740..b7edd5ad59 100644 --- a/src/renderer/src/hooks/useAssistant.ts +++ b/src/renderer/src/hooks/useAssistant.ts @@ -17,7 +17,7 @@ import { } from '@renderer/store/assistants' import { setDefaultModel, setTopicNamingModel, setTranslateModel } from '@renderer/store/llm' import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types' -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' import { TopicManager } from './useTopic' @@ -84,11 +84,12 @@ export function useAssistant(id: string) { export function useDefaultAssistant() { const defaultAssistant = useAppSelector((state) => state.assistants.defaultAssistant) const dispatch = useAppDispatch() + const memoizedTopics = useMemo(() => [getDefaultTopic(defaultAssistant.id)], [defaultAssistant.id]) return { defaultAssistant: { ...defaultAssistant, - topics: [getDefaultTopic(defaultAssistant.id)] + topics: memoizedTopics }, updateDefaultAssistant: (assistant: Assistant) => dispatch(updateDefaultAssistant({ assistant })) } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 699eec0858..8877c8de72 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -335,7 +335,8 @@ "title": "Render Error" }, "user_message_not_found": "Cannot find original user message to resend", - "unknown": "Unknown error" + "unknown": "Unknown error", + "pause_placeholder": "Paused" }, "export": { "assistant": "Assistant", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 5f74e96c75..5264cecd30 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -335,7 +335,8 @@ "title": "レンダリングエラー" }, "user_message_not_found": "元のユーザーメッセージを見つけることができませんでした", - "unknown": "不明なエラー" + "unknown": "不明なエラー", + "pause_placeholder": "応答を一時停止しました" }, "export": { "assistant": "アシスタント", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 74f848b996..68022d75ef 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -335,7 +335,8 @@ "title": "Ошибка рендеринга" }, "user_message_not_found": "Не удалось найти исходное сообщение пользователя", - "unknown": "Неизвестная ошибка" + "unknown": "Неизвестная ошибка", + "pause_placeholder": "Получение ответа приостановлено" }, "export": { "assistant": "Ассистент", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 7aeb3ecad5..ab91f38448 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -335,7 +335,8 @@ "title": "渲染错误" }, "user_message_not_found": "无法找到原始用户消息", - "unknown": "未知错误" + "unknown": "未知错误", + "pause_placeholder": "已中断" }, "export": { "assistant": "助手", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 12ae3ca426..b2bf98d40d 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -335,7 +335,8 @@ "title": "渲染錯誤" }, "user_message_not_found": "無法找到原始用戶訊息", - "unknown": "未知錯誤" + "unknown": "未知錯誤", + "pause_placeholder": "回應已暫停" }, "export": { "assistant": "助手", diff --git a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx index 77647b8dea..2c8161397d 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx @@ -1,14 +1,35 @@ import type { ErrorMessageBlock } from '@renderer/types/newMessage' +import { Alert as AntdAlert } from 'antd' import React from 'react' - -import MessageError from '../MessageError' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' interface Props { block: ErrorMessageBlock } const ErrorBlock: React.FC = ({ block }) => { - return + return } +const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock }> = ({ block }) => { + const { t, i18n } = useTranslation() + const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504] + if (block.error && HTTP_ERROR_CODES.includes(block.error?.status)) { + return + } + if (block?.error?.message) { + const errorKey = `error.${block.error.message}` + const pauseErrorLanguagePlaceholder = i18n.exists(errorKey) ? t(errorKey) : block.error.message + + return + } + + return +} +const Alert = styled(AntdAlert)` + margin: 15px 0 8px; + padding: 10px; + font-size: 12px; +` export default React.memo(ErrorBlock) diff --git a/src/renderer/src/pages/home/Messages/Blocks/MainTextBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/MainTextBlock.tsx index b52449d4a7..f228694061 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/MainTextBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/MainTextBlock.tsx @@ -26,7 +26,6 @@ const encodeHTML = (str: string): string => { interface Props { block: MainTextMessageBlock citationBlockId?: string - model?: Model mentions?: Model[] role: Message['role'] } diff --git a/src/renderer/src/pages/home/Messages/Blocks/index.tsx b/src/renderer/src/pages/home/Messages/Blocks/index.tsx index 6914d3c2bd..c0d3bf716f 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/index.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/index.tsx @@ -1,6 +1,5 @@ import type { RootState } from '@renderer/store' import { messageBlocksSelectors } from '@renderer/store/messageBlock' -import type { Model } from '@renderer/types' import type { ErrorMessageBlock, FileMessageBlock, @@ -28,16 +27,13 @@ import TranslationBlock from './TranslationBlock' interface Props { blocks: MessageBlock[] | string[] // 可以接收块ID数组或MessageBlock数组 - model?: Model messageStatus?: Message['status'] message: Message } -const MessageBlockRenderer: React.FC = ({ blocks, model, message }) => { +const MessageBlockRenderer: React.FC = ({ blocks, message }) => { // 始终调用useSelector,避免条件调用Hook const blockEntities = useSelector((state: RootState) => messageBlocksSelectors.selectEntities(state)) - // if (!blocks || blocks.length === 0) return null - // 根据blocks类型处理渲染数据 const renderedBlocks = blocks.map((blockId) => blockEntities[blockId]).filter(Boolean) return ( @@ -61,7 +57,6 @@ const MessageBlockRenderer: React.FC = ({ blocks, model, message }) => { = ({ className="message-content-container" style={{ fontFamily, fontSize, background: messageBackground, overflowY: 'visible' }}> - + {showMenubar && ( = ({ message, model }) => { +const MessageContent: React.FC = ({ message }) => { // const { t } = useTranslation() // if (message.status === 'pending') { // return ( @@ -46,7 +44,7 @@ const MessageContent: React.FC = ({ message, model }) => { {message.mentions?.map((model) => {'@' + model.name})} - + ) } diff --git a/src/renderer/src/store/messageBlock.ts b/src/renderer/src/store/messageBlock.ts index 729c814687..354e91a4c2 100644 --- a/src/renderer/src/store/messageBlock.ts +++ b/src/renderer/src/store/messageBlock.ts @@ -154,7 +154,7 @@ const formatCitationsFromBlock = (block: CitationMessageBlock | undefined): Cita break case WebSearchSource.WEBSEARCH: formattedCitations = - (block.response.results as WebSearchProviderResponse)?.results.map((result, index) => ({ + (block.response.results as WebSearchProviderResponse)?.results?.map((result, index) => ({ number: index + 1, url: result.url, title: result.title, diff --git a/src/renderer/src/store/newMessage.ts b/src/renderer/src/store/newMessage.ts index 4865153cab..a33d31cdfc 100644 --- a/src/renderer/src/store/newMessage.ts +++ b/src/renderer/src/store/newMessage.ts @@ -82,7 +82,6 @@ const messagesSlice = createSlice({ const { topicId, messages } = action.payload messagesAdapter.upsertMany(state, messages) state.messageIdsByTopic[topicId] = messages.map((m) => m.id) - state.loadingByTopic[topicId] = false }, addMessage(state, action: PayloadAction<{ topicId: string; message: Message }>) { const { topicId, message } = action.payload diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts index cd6f37095f..b3b07c77ae 100644 --- a/src/renderer/src/store/thunk/messageThunk.ts +++ b/src/renderer/src/store/thunk/messageThunk.ts @@ -17,6 +17,7 @@ import type { } from '@renderer/types/newMessage' import { AssistantMessageStatus, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage' import { Response } from '@renderer/types/newMessage' +import { isAbortError } from '@renderer/utils/error' import { extractUrlsFromMarkdown } from '@renderer/utils/linkConverter' import { createAssistantMessage, @@ -556,12 +557,19 @@ const fetchAndProcessAssistantResponseImpl = async ( } }, onError: (error) => { - console.error('Stream processing error:', error) + console.dir(error, { depth: null }) + let pauseErrorLanguagePlaceholder = '' + if (isAbortError(error)) { + pauseErrorLanguagePlaceholder = 'pause_placeholder' + } + const serializableError = { name: error.name, - message: error.message || 'Stream processing error', + message: pauseErrorLanguagePlaceholder || error.message || 'Stream processing error', originalMessage: error.message, - stack: error.stack + stack: error.stack, + status: error.status, + requestId: error.request_id } if (lastBlockId) { // 更改上一个block的状态为ERROR @@ -705,17 +713,11 @@ export const loadTopicMessagesThunk = async (dispatch: AppDispatch, getState: () => RootState) => { const state = getState() const topicMessagesExist = !!state.messages.messageIdsByTopic[topicId] - const isLoading = state.messages.loadingByTopic[topicId] - if ((topicMessagesExist && !forceReload) || isLoading) { - if (topicMessagesExist && isLoading) { - dispatch(newMessagesActions.setTopicLoading({ topicId, loading: false })) - } + if (topicMessagesExist && !forceReload) { return } - dispatch(newMessagesActions.setTopicLoading({ topicId, loading: true })) - dispatch(newMessagesActions.setCurrentTopicId(topicId)) try { const topic = await db.topics.get(topicId) const messagesFromDB = topic?.messages || [] @@ -737,7 +739,7 @@ export const loadTopicMessagesThunk = } } catch (error: any) { console.error(`[loadTopicMessagesThunk] Failed to load messages for topic ${topicId}:`, error) - dispatch(newMessagesActions.setTopicLoading({ topicId, loading: false })) + // dispatch(newMessagesActions.setTopicLoading({ topicId, loading: false })) } } diff --git a/src/renderer/src/windows/mini/chat/ChatWindow.tsx b/src/renderer/src/windows/mini/chat/ChatWindow.tsx index 3efdafd44e..2156d405b5 100644 --- a/src/renderer/src/windows/mini/chat/ChatWindow.tsx +++ b/src/renderer/src/windows/mini/chat/ChatWindow.tsx @@ -1,21 +1,21 @@ import Scrollbar from '@renderer/components/Scrollbar' -import { useDefaultAssistant } from '@renderer/hooks/useAssistant' import { getDefaultModel } from '@renderer/services/AssistantService' +import { Assistant } from '@renderer/types' import { FC } from 'react' import styled from 'styled-components' import Messages from './components/Messages' - interface Props { route: string + assistant: Assistant } -const ChatWindow: FC = ({ route }) => { - const { defaultAssistant } = useDefaultAssistant() +const ChatWindow: FC = ({ route, assistant }) => { + // const { defaultAssistant } = useDefaultAssistant() return (
- +
) } diff --git a/src/renderer/src/windows/mini/chat/components/Message.tsx b/src/renderer/src/windows/mini/chat/components/Message.tsx index 31b184b52a..149b9de3d7 100644 --- a/src/renderer/src/windows/mini/chat/components/Message.tsx +++ b/src/renderer/src/windows/mini/chat/components/Message.tsx @@ -1,18 +1,12 @@ import { FONT_FAMILY } from '@renderer/config/constant' -import { useModel } from '@renderer/hooks/useModel' import { useSettings } from '@renderer/hooks/useSettings' +// import MessageContent from './MessageContent' import MessageContent from '@renderer/pages/home/Messages/MessageContent' import MessageErrorBoundary from '@renderer/pages/home/Messages/MessageErrorBoundary' -import { fetchChatCompletion } from '@renderer/services/ApiService' -import { getDefaultAssistant, getDefaultModel } from '@renderer/services/AssistantService' -import { getMessageModelId } from '@renderer/services/MessagesService' -import { Chunk, ChunkType } from '@renderer/types/chunk' // import { LegacyMessage } from '@renderer/types' -import type { MainTextMessageBlock, Message } from '@renderer/types/newMessage' -import { AssistantMessageStatus, MessageBlockStatus } from '@renderer/types/newMessage' +import type { Message } from '@renderer/types/newMessage' import { isMiniWindow } from '@renderer/utils' -import { createAssistantMessage, createMainTextBlock } from '@renderer/utils/messageUtils/create' -import { Dispatch, FC, memo, SetStateAction, useEffect, useMemo, useRef, useState } from 'react' +import { FC, memo, useMemo, useRef } from 'react' import styled from 'styled-components' interface Props { @@ -20,17 +14,15 @@ interface Props { index?: number total: number route: string - onGetMessages?: () => Message[] - onSetMessages?: Dispatch> } const getMessageBackground = (isBubbleStyle: boolean, isAssistantMessage: boolean) => isBubbleStyle ? (isAssistantMessage ? 'transparent' : 'var(--chat-background-user)') : undefined -const MessageItem: FC = ({ message: _message, index, total, route, onSetMessages, onGetMessages }) => { - const [message, setMessage] = useState(_message) - const [textBlock, setTextBlock] = useState(null) - const model = useModel(getMessageModelId(message)) +const MessageItem: FC = ({ message, index, total, route }) => { + // const [message, setMessage] = useState(_message) + // const [bl, setTextBlock] = useState(null) + // const model = useModel(getMessageModelId(message)) const isBubbleStyle = true const { messageFont, fontSize } = useSettings() const messageContainerRef = useRef(null) @@ -45,43 +37,6 @@ const MessageItem: FC = ({ message: _message, index, total, route, onSetM const maxWidth = isMiniWindow() ? '800px' : '100%' - useEffect(() => { - if (onGetMessages && onSetMessages) { - if (message.status === AssistantMessageStatus.PROCESSING) { - const messages = onGetMessages() - const assistant = getDefaultAssistant() - fetchChatCompletion({ - messages: messages - .filter((m) => !m.status.includes('ing')) - .slice( - 0, - messages.findIndex((m) => m.id === message.id) - ), - assistant: { ...assistant, model: getDefaultModel() }, - onChunkReceived: (chunk: Chunk) => { - if (chunk.type === ChunkType.TEXT_DELTA) { - if (!textBlock) { - const block = createMainTextBlock(message.id, chunk.text, { status: MessageBlockStatus.STREAMING }) - const assistantMessage = createAssistantMessage(assistant.id, message.topicId, { - blocks: [block.id] - }) - setTextBlock(block) - setMessage(assistantMessage) - } else { - setTextBlock((prev) => { - if (prev) { - return { ...prev, content: (prev?.content ?? '') + chunk.text } - } - return null - }) - } - } - } - }) - } - } - }, [message.status, message.topicId, textBlock, message.id, onGetMessages, onSetMessages]) - if (['summary', 'explanation'].includes(route) && index === total - 1) { return null } @@ -100,7 +55,7 @@ const MessageItem: FC = ({ message: _message, index, total, route, onSetM ...(isAssistantMessage ? { paddingLeft: 5, paddingRight: 5 } : {}) }}> - + diff --git a/src/renderer/src/windows/mini/chat/components/MessageContent.tsx b/src/renderer/src/windows/mini/chat/components/MessageContent.tsx index 9b1d0a783a..1d0bece9ab 100644 --- a/src/renderer/src/windows/mini/chat/components/MessageContent.tsx +++ b/src/renderer/src/windows/mini/chat/components/MessageContent.tsx @@ -1,28 +1,25 @@ import Markdown from '@renderer/pages/home/Markdown/Markdown' -import { getModelUniqId } from '@renderer/services/ModelService' -import type { MainTextMessageBlock, Message } from '@renderer/types/newMessage' -import { Flex } from 'antd' +import type { MainTextMessageBlock } from '@renderer/types/newMessage' import React from 'react' -import styled from 'styled-components' interface Props { - message: Message block: MainTextMessageBlock } -const MessageContent: React.FC = ({ message, block }) => { +const MessageContent: React.FC = ({ block }) => { + console.log('block', block) return ( <> - + {/* {message.mentions?.map((model) => {'@' + model.name})} - + */} ) } -const MentionTag = styled.span` - color: var(--color-link); -` +// const MentionTag = styled.span` +// color: var(--color-link); +// ` export default React.memo(MessageContent) diff --git a/src/renderer/src/windows/mini/chat/components/Messages.tsx b/src/renderer/src/windows/mini/chat/components/Messages.tsx index 9b97e0dba4..2030424a27 100644 --- a/src/renderer/src/windows/mini/chat/components/Messages.tsx +++ b/src/renderer/src/windows/mini/chat/components/Messages.tsx @@ -1,11 +1,9 @@ import Scrollbar from '@renderer/components/Scrollbar' -import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' -import { getAssistantMessage } from '@renderer/services/MessagesService' +import { useTopicMessages } from '@renderer/hooks/useMessageOperations' import { Assistant } from '@renderer/types' -import type { Message } from '@renderer/types/newMessage' import { getMainTextContent } from '@renderer/utils/messageUtils/find' import { last } from 'lodash' -import { FC, useCallback, useEffect, useRef, useState } from 'react' +import { FC, useRef } from 'react' import { useHotkeys } from 'react-hotkeys-hook' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -21,8 +19,8 @@ interface ContainerProps { } const Messages: FC = ({ assistant, route }) => { - const [messages, setMessages] = useState([]) - + // const [messages, setMessages] = useState([]) + const messages = useTopicMessages(assistant.topics[0]) const containerRef = useRef(null) const messagesRef = useRef(messages) @@ -30,25 +28,23 @@ const Messages: FC = ({ assistant, route }) => { messagesRef.current = messages - const onSendMessage = useCallback( - async (message: Message) => { - setMessages((prev) => { - const assistantMessage = getAssistantMessage({ assistant, topic: assistant.topics[0] }) - const messages = prev.concat([message, assistantMessage]) - return messages - }) - }, - [assistant] - ) + // const onSendMessage = useCallback( + // async (message: Message) => { + // setMessages((prev) => { + // const assistantMessage = getAssistantMessage({ assistant, topic: assistant.topics[0] }) + // store.dispatch(newMessagesActions.addMessage({ topicId: assistant.topics[0].id, message: assistantMessage })) - const onGetMessages = useCallback(() => { - return messagesRef.current - }, []) + // const messages = prev.concat([message, assistantMessage]) + // return messages + // }) + // }, + // [assistant] + // ) - useEffect(() => { - const unsubscribes = [EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, onSendMessage)] - return () => unsubscribes.forEach((unsub) => unsub()) - }, [assistant.id, onSendMessage]) + // useEffect(() => { + // const unsubscribes = [EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, onSendMessage)] + // return () => unsubscribes.forEach((unsub) => unsub()) + // }, [assistant.id]) useHotkeys('c', () => { const lastMessage = last(messages) @@ -58,19 +54,10 @@ const Messages: FC = ({ assistant, route }) => { window.message.success(t('message.copy.success')) } }) - return ( {[...messages].reverse().map((message, index) => ( - + ))} ) diff --git a/src/renderer/src/windows/mini/home/HomeWindow.tsx b/src/renderer/src/windows/mini/home/HomeWindow.tsx index 332205a4e9..de24d7ae8f 100644 --- a/src/renderer/src/windows/mini/home/HomeWindow.tsx +++ b/src/renderer/src/windows/mini/home/HomeWindow.tsx @@ -2,8 +2,17 @@ import { isMac } from '@renderer/config/constant' import { useDefaultAssistant, useDefaultModel } from '@renderer/hooks/useAssistant' import { useSettings } from '@renderer/hooks/useSettings' import i18n from '@renderer/i18n' -import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' -import { uuid } from '@renderer/utils' +import { fetchChatCompletion } from '@renderer/services/ApiService' +import { getDefaultAssistant, getDefaultModel } from '@renderer/services/AssistantService' +import { getAssistantMessage, getUserMessage } from '@renderer/services/MessagesService' +import store from '@renderer/store' +import { upsertManyBlocks } from '@renderer/store/messageBlock' +import { updateOneBlock, upsertOneBlock } from '@renderer/store/messageBlock' +import { newMessagesActions } from '@renderer/store/newMessage' +import { Chunk, ChunkType } from '@renderer/types/chunk' +import { AssistantMessageStatus } from '@renderer/types/newMessage' +import { MessageBlockStatus } from '@renderer/types/newMessage' +import { createMainTextBlock } from '@renderer/utils/messageUtils/create' import { defaultLanguage } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' import { Divider } from 'antd' @@ -30,12 +39,12 @@ const HomeWindow: FC = () => { const [lastClipboardText, setLastClipboardText] = useState(null) const textChange = useState(() => {})[1] const { defaultAssistant } = useDefaultAssistant() + const topic = defaultAssistant.topics[0] const { defaultModel: model } = useDefaultModel() const { language, readClipboardAtStartup, windowStyle, theme } = useSettings() const { t } = useTranslation() const inputBarRef = useRef(null) const featureMenusRef = useRef(null) - const referenceText = selectedText || clipboardText || text const content = isFirstMessage ? (referenceText === text ? text : `${referenceText}\n\n${text}`).trim() : text.trim() @@ -147,23 +156,68 @@ const HomeWindow: FC = () => { return } - setTimeout(() => { - const message = { - id: uuid(), - role: 'user', - content: prompt ? `${prompt}\n\n${content}` : content, - assistantId: defaultAssistant.id, - topicId: defaultAssistant.topics[0].id || uuid(), - createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'), - type: 'text', - status: 'success' + const messageParams = { + role: 'user', + content: prompt ? `${prompt}\n\n${content}` : content, + assistant: defaultAssistant, + topic, + createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'), + status: 'success' + } + const topicId = topic.id + const { message: userMessage, blocks } = getUserMessage(messageParams) + + store.dispatch(newMessagesActions.addMessage({ topicId, message: userMessage })) + store.dispatch(upsertManyBlocks(blocks)) + + const assistant = getDefaultAssistant() + let blockId: string | null = null + let blockContent: string = '' + + const assistantMessage = getAssistantMessage({ assistant, topic: assistant.topics[0] }) + store.dispatch(newMessagesActions.addMessage({ topicId, message: assistantMessage })) + + fetchChatCompletion({ + messages: [userMessage], + assistant: { ...assistant, model: getDefaultModel() }, + onChunkReceived: (chunk: Chunk) => { + console.log('chunk', chunk) + if (chunk.type === ChunkType.TEXT_DELTA) { + blockContent += chunk.text + if (!blockId) { + const block = createMainTextBlock(assistantMessage.id, chunk.text, { + status: MessageBlockStatus.STREAMING + }) + blockId = block.id + store.dispatch( + newMessagesActions.updateMessage({ + topicId, + messageId: assistantMessage.id, + updates: { blockInstruction: { id: block.id } } + }) + ) + store.dispatch(upsertOneBlock(block)) + } else { + store.dispatch(updateOneBlock({ id: blockId, changes: { content: blockContent } })) + } + } + if (chunk.type === ChunkType.TEXT_COMPLETE) { + blockId && store.dispatch(updateOneBlock({ id: blockId, changes: { status: MessageBlockStatus.SUCCESS } })) + store.dispatch( + newMessagesActions.updateMessage({ + topicId, + messageId: assistantMessage.id, + updates: { status: AssistantMessageStatus.SUCCESS } + }) + ) + } } - EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE, message) - setIsFirstMessage(false) - setText('') // ✅ 清除输入框内容 - }, 0) + }) + + setIsFirstMessage(false) + setText('') // ✅ 清除输入框内容 }, - [content, defaultAssistant.id, defaultAssistant.topics] + [content, defaultAssistant] ) const clearClipboard = () => { @@ -240,7 +294,7 @@ const HomeWindow: FC = () => { )} - +