Compare commits

...

3 Commits

Author SHA1 Message Date
yyh
c0a76220dd fix(skill-editor): resolve React Compiler memoization warnings
Consolidate file type derivations into a single useMemo with stable
dependencies (currentFileNode?.name and currentFileNode?.extension)
to help React Compiler track stability.

Extract originalContent as a separate variable to avoid property access
in useCallback dependencies, which caused Compiler to infer broader
dependencies than specified.
2026-01-17 22:01:33 +08:00
yyh
9d04fb4992 fix(skill-editor): resolve React Compiler memoization warnings
Wrap isEditable in useMemo to help React Compiler track its stability
and preserve memoization for callbacks that depend on it. Also replace
Record<string, any> with Record<string, unknown> to satisfy no-explicit-any.
2026-01-17 21:51:25 +08:00
yyh
02fcf33067 fix(skill-editor): remove unnecessary store subscriptions in tool-picker-block
Move activeTabId and fileMetadata reads from selector subscriptions to
getState() calls inside the callback. These values were only used in the
insertTools callback, not for rendering, causing unnecessary re-renders
when they changed.
2026-01-17 21:47:31 +08:00
2 changed files with 29 additions and 24 deletions

View File

@ -17,7 +17,7 @@ import { $splitNodeContainingQuery } from '@/app/components/base/prompt-editor/u
import { CollectionType } from '@/app/components/tools/types'
import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
import ToolPicker from '@/app/components/workflow/block-selector/tool-picker'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { $createToolBlockNode } from './node'
class ToolPickerMenuOption extends MenuOption {
@ -36,8 +36,6 @@ const ToolPickerBlock: FC<ToolPickerBlockProps> = ({ scope = 'all' }) => {
minLength: 0,
maxLength: 0,
})
const activeTabId = useStore(s => s.activeTabId)
const fileMetadata = useStore(s => s.fileMetadata)
const storeApi = useWorkflowStore()
const options = useMemo(() => [new ToolPickerMenuOption()], [])
@ -73,11 +71,10 @@ const ToolPickerBlock: FC<ToolPickerBlockProps> = ({ scope = 'all' }) => {
$insertNodes(nodes)
})
const storeState = storeApi.getState()
const resolvedTabId = activeTabId || storeState.activeTabId
if (!resolvedTabId)
const { activeTabId, fileMetadata, setDraftMetadata, pinTab } = storeApi.getState()
if (!activeTabId)
return
const metadata = (storeState.fileMetadata.get(resolvedTabId) || {}) as Record<string, any>
const metadata = (fileMetadata.get(activeTabId) || {}) as Record<string, any>
const nextTools = { ...(metadata.tools || {}) } as Record<string, any>
toolEntries.forEach(({ configId, tool }) => {
const schemas = toolParametersToFormSchemas((tool.paramSchemas || []) as ToolParameter[])
@ -91,12 +88,12 @@ const ToolPickerBlock: FC<ToolPickerBlockProps> = ({ scope = 'all' }) => {
configuration: { fields },
}
})
storeState.setDraftMetadata(resolvedTabId, {
setDraftMetadata(activeTabId, {
...metadata,
tools: nextTools,
})
storeState.pinTab(resolvedTabId)
}, [activeTabId, checkForTriggerMatch, editor, fileMetadata, storeApi])
pinTab(activeTabId)
}, [checkForTriggerMatch, editor, storeApi])
const renderMenu = useCallback((
anchorElementRef: React.RefObject<HTMLElement | null>,

View File

@ -42,13 +42,20 @@ const SkillDocEditor: FC = () => {
const { data: nodeMap } = useSkillAssetNodeMap()
const currentFileNode = activeTabId ? nodeMap?.get(activeTabId) : undefined
const fileExtension = getFileExtension(currentFileNode?.name, currentFileNode?.extension)
const isMarkdown = isMarkdownFile(fileExtension)
const isCodeOrText = isCodeOrTextFile(fileExtension)
const isImage = isImageFile(fileExtension)
const isVideo = isVideoFile(fileExtension)
const isOffice = isOfficeFile(fileExtension)
const isEditable = isMarkdown || isCodeOrText
const { isMarkdown, isCodeOrText, isImage, isVideo, isOffice, isEditable } = useMemo(() => {
const ext = getFileExtension(currentFileNode?.name, currentFileNode?.extension)
const markdown = isMarkdownFile(ext)
const codeOrText = isCodeOrTextFile(ext)
return {
isMarkdown: markdown,
isCodeOrText: codeOrText,
isImage: isImageFile(ext),
isVideo: isVideoFile(ext),
isOffice: isOfficeFile(ext),
isEditable: markdown || codeOrText,
}
}, [currentFileNode?.name, currentFileNode?.extension])
const {
data: fileContent,
@ -58,14 +65,16 @@ const SkillDocEditor: FC = () => {
const updateContent = useUpdateAppAssetFileContent()
const originalContent = fileContent?.content ?? ''
const currentContent = useMemo(() => {
if (!activeTabId)
return ''
const draft = dirtyContents.get(activeTabId)
if (draft !== undefined)
return draft
return fileContent?.content ?? ''
}, [activeTabId, dirtyContents, fileContent?.content])
return originalContent
}, [activeTabId, dirtyContents, originalContent])
const currentMetadata = useMemo(() => {
if (!activeTabId)
@ -78,7 +87,7 @@ const SkillDocEditor: FC = () => {
return
if (dirtyMetadataIds.has(activeTabId))
return
let nextMetadata: Record<string, any> = {}
let nextMetadata: Record<string, unknown> = {}
if (fileContent.metadata) {
if (typeof fileContent.metadata === 'string') {
try {
@ -100,7 +109,6 @@ const SkillDocEditor: FC = () => {
if (!activeTabId || !isEditable)
return
const newValue = value ?? ''
const originalContent = fileContent?.content ?? ''
if (newValue === originalContent)
storeApi.getState().clearDraftContent(activeTabId)
@ -108,7 +116,7 @@ const SkillDocEditor: FC = () => {
storeApi.getState().setDraftContent(activeTabId, newValue)
storeApi.getState().pinTab(activeTabId)
}, [activeTabId, isEditable, storeApi, fileContent?.content])
}, [activeTabId, isEditable, originalContent, storeApi])
const handleSave = useCallback(async () => {
if (!activeTabId || !appId || !isEditable)
@ -124,7 +132,7 @@ const SkillDocEditor: FC = () => {
appId,
nodeId: activeTabId,
payload: {
content: content ?? fileContent?.content ?? '',
content: content ?? originalContent,
...(currentMetadata ? { metadata: currentMetadata } : {}),
},
})
@ -141,7 +149,7 @@ const SkillDocEditor: FC = () => {
message: String(error),
})
}
}, [activeTabId, appId, currentMetadata, dirtyContents, dirtyMetadataIds, fileContent?.content, isEditable, storeApi, t, updateContent])
}, [activeTabId, appId, currentMetadata, dirtyContents, dirtyMetadataIds, isEditable, originalContent, storeApi, t, updateContent])
useEffect(() => {
function handleKeyDown(e: KeyboardEvent): void {