mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 06:49:02 +08:00
refactor(code-block): extract code block editing logic to custom hook
Move code block editing functionality from Messages component to useEditCodeBlock hook for better reusability and maintainability
This commit is contained in:
parent
d2b25af146
commit
d3d02712a4
@ -39,7 +39,7 @@ interface Props {
|
|||||||
language: string
|
language: string
|
||||||
// Message Block ID
|
// Message Block ID
|
||||||
blockId: string
|
blockId: string
|
||||||
onSave: (newContent: string) => void
|
onSave: (newContent: string) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -25,7 +25,7 @@ interface UseMermaidFixTool {
|
|||||||
/** Mermaid code */
|
/** Mermaid code */
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
onSave: (newContent: string) => void
|
onSave: (newContent: string) => Promise<void>
|
||||||
setError: (error: unknown) => void
|
setError: (error: unknown) => void
|
||||||
setTools: React.Dispatch<React.SetStateAction<ActionTool[]>>
|
setTools: React.Dispatch<React.SetStateAction<ActionTool[]>>
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ export const useMermaidFixTool = ({ enabled, context, onSave, setError, setTools
|
|||||||
if (parsedResult.success) {
|
if (parsedResult.success) {
|
||||||
const validResult = parsedResult.data
|
const validResult = parsedResult.data
|
||||||
if (validResult.fixed) {
|
if (validResult.fixed) {
|
||||||
onSave(validResult.result)
|
await onSave(validResult.result)
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
} else {
|
} else {
|
||||||
window.toast.warning({ title: t('code_block.mermaid_fix.failed'), description: validResult.reason })
|
window.toast.warning({ title: t('code_block.mermaid_fix.failed'), description: validResult.reason })
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { useStore } from 'react-redux'
|
import { useStore } from 'react-redux'
|
||||||
const logger = loggerService.withContext('useChatContext')
|
const logger = loggerService.withContext('useChatContext')
|
||||||
|
|
||||||
|
// TODO: use useContext to refactor it.
|
||||||
export const useChatContext = (activeTopic: Topic) => {
|
export const useChatContext = (activeTopic: Topic) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const store = useStore<RootState>()
|
const store = useStore<RootState>()
|
||||||
|
|||||||
55
src/renderer/src/hooks/useEditCodeBlock.ts
Normal file
55
src/renderer/src/hooks/useEditCodeBlock.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import store, { useAppDispatch } from '@renderer/store'
|
||||||
|
import { messageBlocksSelectors, updateOneBlock } from '@renderer/store/messageBlock'
|
||||||
|
import { updateMessageAndBlocksThunk } from '@renderer/store/thunk/messageThunk'
|
||||||
|
import type { MessageBlock } from '@renderer/types/newMessage'
|
||||||
|
import { MessageBlockType } from '@renderer/types/newMessage'
|
||||||
|
import { updateCodeBlock } from '@renderer/utils/markdown'
|
||||||
|
import { isTextLikeBlock } from '@renderer/utils/messageUtils/is'
|
||||||
|
import { t } from 'i18next'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('useEditCodeBlock')
|
||||||
|
|
||||||
|
export const useEditCodeBlock = () => {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const editCodeBlock = useCallback(
|
||||||
|
async (data: { topicId: string; msgBlockId: string; codeBlockId: string; newContent: string }) => {
|
||||||
|
const { topicId, msgBlockId, codeBlockId, newContent } = data
|
||||||
|
|
||||||
|
const msgBlock = messageBlocksSelectors.selectById(store.getState(), msgBlockId)
|
||||||
|
|
||||||
|
// FIXME: 目前 error block 没有 content
|
||||||
|
if (msgBlock && isTextLikeBlock(msgBlock) && msgBlock.type !== MessageBlockType.ERROR) {
|
||||||
|
try {
|
||||||
|
const updatedRaw = updateCodeBlock(msgBlock.content, codeBlockId, newContent)
|
||||||
|
const updatedBlock: MessageBlock = {
|
||||||
|
...msgBlock,
|
||||||
|
content: updatedRaw,
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(updateOneBlock({ id: msgBlockId, changes: { content: updatedRaw } }))
|
||||||
|
await dispatch(updateMessageAndBlocksThunk(topicId, null, [updatedBlock]))
|
||||||
|
|
||||||
|
window.toast.success(t('code_block.edit.save.success'))
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`Failed to save code block ${codeBlockId} content to message block ${msgBlockId}:`,
|
||||||
|
error as Error
|
||||||
|
)
|
||||||
|
window.toast.error(t('code_block.edit.save.failed.label'))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error(
|
||||||
|
`Failed to save code block ${codeBlockId} content to message block ${msgBlockId}: no such message block or the block doesn't have a content field`
|
||||||
|
)
|
||||||
|
window.toast.error(t('code_block.edit.save.failed.label'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
)
|
||||||
|
|
||||||
|
return editCodeBlock
|
||||||
|
}
|
||||||
@ -34,7 +34,7 @@ const CodeBlock: React.FC<Props> = ({ children, className, node, blockId }) => {
|
|||||||
const isStreaming = useMemo(() => msgBlock?.status === MessageBlockStatus.STREAMING, [msgBlock?.status])
|
const isStreaming = useMemo(() => msgBlock?.status === MessageBlockStatus.STREAMING, [msgBlock?.status])
|
||||||
|
|
||||||
const handleSave = useCallback(
|
const handleSave = useCallback(
|
||||||
(newContent: string) => {
|
async (newContent: string) => {
|
||||||
if (id !== undefined) {
|
if (id !== undefined) {
|
||||||
EventEmitter.emit(EVENT_NAMES.EDIT_CODE_BLOCK, {
|
EventEmitter.emit(EVENT_NAMES.EDIT_CODE_BLOCK, {
|
||||||
msgBlockId: blockId,
|
msgBlockId: blockId,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { LoadingIcon } from '@renderer/components/Icons'
|
|||||||
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
|
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useChatContext } from '@renderer/hooks/useChatContext'
|
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||||
|
import { useEditCodeBlock } from '@renderer/hooks/useEditCodeBlock'
|
||||||
import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
||||||
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
||||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||||
@ -15,22 +16,18 @@ import { getDefaultTopic } from '@renderer/services/AssistantService'
|
|||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { getContextCount, getGroupedMessages, getUserMessage } from '@renderer/services/MessagesService'
|
import { getContextCount, getGroupedMessages, getUserMessage } from '@renderer/services/MessagesService'
|
||||||
import { estimateHistoryTokens } from '@renderer/services/TokenService'
|
import { estimateHistoryTokens } from '@renderer/services/TokenService'
|
||||||
import store, { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { messageBlocksSelectors, updateOneBlock } from '@renderer/store/messageBlock'
|
|
||||||
import { newMessagesActions } from '@renderer/store/newMessage'
|
import { newMessagesActions } from '@renderer/store/newMessage'
|
||||||
import { saveMessageAndBlocksToDB, updateMessageAndBlocksThunk } from '@renderer/store/thunk/messageThunk'
|
import { saveMessageAndBlocksToDB } from '@renderer/store/thunk/messageThunk'
|
||||||
import type { Assistant, Topic } from '@renderer/types'
|
import type { Assistant, Topic } from '@renderer/types'
|
||||||
import type { MessageBlock } from '@renderer/types/newMessage'
|
import { type Message } from '@renderer/types/newMessage'
|
||||||
import { type Message, MessageBlockType } from '@renderer/types/newMessage'
|
|
||||||
import {
|
import {
|
||||||
captureScrollableAsBlob,
|
captureScrollableAsBlob,
|
||||||
captureScrollableAsDataURL,
|
captureScrollableAsDataURL,
|
||||||
removeSpecialCharactersForFileName,
|
removeSpecialCharactersForFileName,
|
||||||
runAsyncFunction
|
runAsyncFunction
|
||||||
} from '@renderer/utils'
|
} from '@renderer/utils'
|
||||||
import { updateCodeBlock } from '@renderer/utils/markdown'
|
|
||||||
import { getMainTextContent } from '@renderer/utils/messageUtils/find'
|
import { getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||||
import { isTextLikeBlock } from '@renderer/utils/messageUtils/is'
|
|
||||||
import { last } from 'lodash'
|
import { last } from 'lodash'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -70,6 +67,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
|
|||||||
const messages = useTopicMessages(topic.id)
|
const messages = useTopicMessages(topic.id)
|
||||||
const { displayCount, clearTopicMessages, deleteMessage, createTopicBranch } = useMessageOperations(topic)
|
const { displayCount, clearTopicMessages, deleteMessage, createTopicBranch } = useMessageOperations(topic)
|
||||||
const { setTimeoutTimer } = useTimer()
|
const { setTimeoutTimer } = useTimer()
|
||||||
|
const editCodeBlock = useEditCodeBlock()
|
||||||
|
|
||||||
const { isMultiSelectMode, handleSelectMessage } = useChatContext(topic)
|
const { isMultiSelectMode, handleSelectMessage } = useChatContext(topic)
|
||||||
|
|
||||||
@ -205,36 +203,12 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
|
|||||||
EVENT_NAMES.EDIT_CODE_BLOCK,
|
EVENT_NAMES.EDIT_CODE_BLOCK,
|
||||||
async (data: { msgBlockId: string; codeBlockId: string; newContent: string }) => {
|
async (data: { msgBlockId: string; codeBlockId: string; newContent: string }) => {
|
||||||
const { msgBlockId, codeBlockId, newContent } = data
|
const { msgBlockId, codeBlockId, newContent } = data
|
||||||
|
return editCodeBlock({
|
||||||
const msgBlock = messageBlocksSelectors.selectById(store.getState(), msgBlockId)
|
topicId: topic.id,
|
||||||
|
msgBlockId,
|
||||||
// FIXME: 目前 error block 没有 content
|
codeBlockId,
|
||||||
if (msgBlock && isTextLikeBlock(msgBlock) && msgBlock.type !== MessageBlockType.ERROR) {
|
newContent
|
||||||
try {
|
})
|
||||||
const updatedRaw = updateCodeBlock(msgBlock.content, codeBlockId, newContent)
|
|
||||||
const updatedBlock: MessageBlock = {
|
|
||||||
...msgBlock,
|
|
||||||
content: updatedRaw,
|
|
||||||
updatedAt: new Date().toISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(updateOneBlock({ id: msgBlockId, changes: { content: updatedRaw } }))
|
|
||||||
await dispatch(updateMessageAndBlocksThunk(topic.id, null, [updatedBlock]))
|
|
||||||
|
|
||||||
window.toast.success(t('code_block.edit.save.success'))
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(
|
|
||||||
`Failed to save code block ${codeBlockId} content to message block ${msgBlockId}:`,
|
|
||||||
error as Error
|
|
||||||
)
|
|
||||||
window.toast.error(t('code_block.edit.save.failed.label'))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.error(
|
|
||||||
`Failed to save code block ${codeBlockId} content to message block ${msgBlockId}: no such message block or the block doesn't have a content field`
|
|
||||||
)
|
|
||||||
window.toast.error(t('code_block.edit.save.failed.label'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user