feat: improve content protection during file operations (#10378)

* feat: improve content protection during file operations

- Add validation for knowledge base configuration before saving
- Enhance error handling for note content reading
- Implement content backup and restoration during file rename
- Add content verification after rename operations
- Improve user feedback with specific error messages

* fix: format check

---------

Co-authored-by: 自由的世界人 <3196812536@qq.com>
This commit is contained in:
Zhaokun 2025-09-26 17:49:24 +08:00 committed by GitHub
parent 52a980f751
commit 4aa9c9f225
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 82 additions and 7 deletions

View File

@ -253,12 +253,39 @@ const PopupContainer: React.FC<Props> = ({ source, title, resolve }) => {
let savedCount = 0
try {
// Validate knowledge base configuration before proceeding
if (!selectedBaseId) {
throw new Error('No knowledge base selected')
}
const selectedBase = bases.find((base) => base.id === selectedBaseId)
if (!selectedBase) {
throw new Error('Selected knowledge base not found')
}
if (!selectedBase.version) {
throw new Error('Knowledge base is not properly configured. Please check the knowledge base settings.')
}
if (isNoteMode) {
const note = source.data as NotesTreeNode
const content = note.externalPath
? await window.api.file.readExternal(note.externalPath)
: await window.api.file.read(note.id + '.md')
logger.debug('Note content:', content)
if (!note.externalPath) {
throw new Error('Note external path is required for export')
}
let content = ''
try {
content = await window.api.file.readExternal(note.externalPath)
} catch (error) {
logger.error('Failed to read note file:', error as Error)
throw new Error('Failed to read note content. Please ensure the file exists and is accessible.')
}
if (!content || content.trim() === '') {
throw new Error('Note content is empty. Cannot export empty notes to knowledge base.')
}
logger.debug('Note content loaded', { contentLength: content.length })
await addNote(content)
savedCount = 1
} else {
@ -283,9 +310,23 @@ const PopupContainer: React.FC<Props> = ({ source, title, resolve }) => {
resolve({ success: true, savedCount })
} catch (error) {
logger.error('save failed:', error as Error)
window.toast.error(
t(isTopicMode ? 'chat.save.topic.knowledge.error.save_failed' : 'chat.save.knowledge.error.save_failed')
// Provide more specific error messages
let errorMessage = t(
isTopicMode ? 'chat.save.topic.knowledge.error.save_failed' : 'chat.save.knowledge.error.save_failed'
)
if (error instanceof Error) {
if (error.message.includes('not properly configured')) {
errorMessage = error.message
} else if (error.message.includes('empty')) {
errorMessage = error.message
} else if (error.message.includes('read note content')) {
errorMessage = error.message
}
}
window.toast.error(errorMessage)
setLoading(false)
}
}

View File

@ -492,10 +492,42 @@ const NotesPage: FC = () => {
if (node && node.name !== newName) {
const oldExternalPath = node.externalPath
let currentContent = ''
// Save current content before rename to prevent content loss
if (node.type === 'file' && activeFilePath === oldExternalPath) {
// Get content from editor or current cache
currentContent = editorRef.current?.getMarkdown() || lastContentRef.current || currentContent
// Save current content to the file before renaming
if (currentContent.trim()) {
try {
await saveCurrentNote(currentContent, oldExternalPath)
} catch (error) {
logger.warn('Failed to save content before rename:', error as Error)
}
}
}
const renamedNode = await renameNode(nodeId, newName)
if (renamedNode.type === 'file' && activeFilePath === oldExternalPath) {
// Restore content to the new file path if content was lost during rename
if (currentContent.trim()) {
try {
const newFileContent = await window.api.file.readExternal(renamedNode.externalPath)
if (!newFileContent || newFileContent.trim() === '') {
await window.api.file.write(renamedNode.externalPath, currentContent)
logger.info('Restored content to renamed file')
}
} catch (error) {
logger.error('Failed to restore content after rename:', error as Error)
}
}
dispatch(setActiveFilePath(renamedNode.externalPath))
// Invalidate cache for the new path to ensure content is loaded correctly
invalidateFileContent(renamedNode.externalPath)
} else if (
renamedNode.type === 'folder' &&
activeFilePath &&
@ -504,6 +536,8 @@ const NotesPage: FC = () => {
const relativePath = activeFilePath.substring(oldExternalPath.length)
const newFilePath = renamedNode.externalPath + relativePath
dispatch(setActiveFilePath(newFilePath))
// Invalidate cache for the new file path after folder rename
invalidateFileContent(newFilePath)
}
await sortAllLevels(sortType)
if (renamedNode.name !== newName) {
@ -518,7 +552,7 @@ const NotesPage: FC = () => {
}, 500)
}
},
[activeFilePath, dispatch, findNodeById, sortType, t]
[activeFilePath, dispatch, findNodeById, sortType, t, invalidateFileContent, saveCurrentNote]
)
// 处理文件上传