优化: 前端体验批次(Knowledge持久化+流式缓存失效 + dfnodes节点注册 + delta 50ms合批 + MCP schema必填校验+bind_directory对齐)

This commit is contained in:
lxy
2026-08-08 20:19:19 +08:00
parent 97525a3143
commit 4092a8d5bb
9 changed files with 277 additions and 90 deletions
+3
View File
@@ -171,6 +171,7 @@ const {
scheduleStreamParse,
clearStreamingState,
cancelPendingRaf,
resetStreamCache,
} = useStreamRenderer({
saveSelection,
restoreSelection,
@@ -715,6 +716,8 @@ watch(() => store.state.activeConversationId, () => {
} else {
cancelPendingRaf()
}
// 切会话同步清流式块级 memo 缓存(_blockCache),防旧会话已缓存块被新会话命中。
resetStreamCache()
})
// 流式 rAF 清理:组件卸载时若仍有 pending rAF(streaming 中途切走/关窗),取消防泄漏。
+7
View File
@@ -207,6 +207,12 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) {
if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null }
}
/// 清空实例级块缓存(内容寻址 Map + LRU)。会话切换/开关变化时调用,
/// 防旧会话已缓存块被新会话命中(跨会话内容错乱)。仅清缓存,不动 streamingBlocks。
function resetStreamCache(): void {
_blockCache.clear()
}
return {
mdReady,
renderMd,
@@ -216,5 +222,6 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) {
renderStreamingBlocks,
clearStreamingState,
cancelPendingRaf,
resetStreamCache,
}
}
+7 -37
View File
@@ -161,6 +161,7 @@ import {
} from '@/stores/knowledge'
import { useRendered } from '@/composables/useMarkdown'
import { useToast } from '@/composables/useToast'
import { usePersistedRef } from '@/composables/usePersistedRef'
import { stripMd } from '@/utils/markdown'
import KnowledgeDetail from '@/components/knowledge/KnowledgeDetail.vue'
import type { KnowledgeDetailPayload, KnowledgeRecord } from '@/api/types'
@@ -170,38 +171,10 @@ const store = useKnowledgeStore()
// ⑤ 编辑/审核反馈:成功/失败明确提示(对齐 Settings.vue/Projects.vue useToast 模式)
const { toast, showToast } = useToast()
// topTab/activeKind/searchQuery 持久化 localStorage(跨刷新保留用户视图态)
// 仅持久化视图态:不持久化详情选中(id 跨刷新可能已失效)。容错:解析失败/无效值回退默认。
const LS_KEY = 'kn.view'
function loadView(): { topTab: 'library' | 'inbox'; activeKind: string; searchQuery: string } {
try {
const raw = localStorage.getItem(LS_KEY)
if (!raw) return { topTab: 'library', activeKind: 'all', searchQuery: '' }
const v = JSON.parse(raw)
return {
topTab: v.topTab === 'inbox' ? 'inbox' : 'library',
activeKind: typeof v.activeKind === 'string' ? v.activeKind : 'all',
searchQuery: typeof v.searchQuery === 'string' ? v.searchQuery : '',
}
} catch {
return { topTab: 'library', activeKind: 'all', searchQuery: '' }
}
}
function saveView() {
try {
localStorage.setItem(LS_KEY, JSON.stringify({
topTab: topTab.value,
activeKind: activeKind.value,
searchQuery: searchQuery.value,
}))
} catch {
/* localStorage 不可用(隐私模式/配额)静默降级,视图态非关键 */
}
}
const persisted = loadView()
// 顶层 Tab: library(知识库) | inbox(审核收件箱)
const topTab = ref<'library' | 'inbox'>(persisted.topTab)
// topTab/activeKind/searchQuery 持久化 localStorage(跨刷新保留用户视图态)
// 仅持久化视图态:不持久化详情选中(id 跨刷新可能已失效)。usePersistedRef 无类型校验,
// 解析失败/无效值回退 default,与 Tasks/Ideas 一致。
const topTab = usePersistedRef<'library' | 'inbox'>('knowledge.topTab', 'library')
const categories = [
{ key: 'all', icon: '📦' },
@@ -211,8 +184,8 @@ const categories = [
function catLabel(key: string): string {
return key === 'all' ? t('knowledge.categoryAll') : kindText(key)
}
const activeKind = ref(persisted.activeKind)
const searchQuery = ref(persisted.searchQuery)
const activeKind = usePersistedRef('knowledge.activeKind', 'all')
const searchQuery = usePersistedRef('knowledge.searchQuery', '')
let searchTimer: ReturnType<typeof setTimeout> | null = null
function onSearchInput() {
@@ -246,9 +219,6 @@ function switchTopTab(tab: 'library' | 'inbox') {
}
}
// ④ 视图态(topTab/activeKind/searchQuery)持久化:任一变化即写 localStorage
watch([topTab, activeKind, searchQuery], saveView)
// 关闭错误条
function clearError() {
store.clearError()